פיתוח ממשק משתמש מרחבי באמצעות Jetpack Compose for XR

מכשירי XR שמתאימים לשימוש
ההנחיות האלה יעזרו לכם ליצור חוויות למכשירי XR מהסוגים הבאים.
משקפי XR
משקפי XR חוטיים

עם Jetpack פיתוח נייטיב ל-XR, אתם יכולים ליצור את ממשק המשתמש המרחבי והפריסה שלכם באופן הצהרתי באמצעות מושגים מוכרים של פיתוח נייטיב, כמו שורות ועמודות. כך תוכלו להרחיב את ממשק המשתמש הקיים שלכם ב-Android למרחב תלת-ממדי או ליצור אפליקציות תלת-ממדיות חדשות לגמרי.

אם אתם מוסיפים תכונות מרחביות לאפליקציה קיימת שמבוססת על Android Views, יש לכם כמה אפשרויות פיתוח. אפשר להשתמש בממשקי API של יכולת פעולה הדדית, להשתמש ב-Compose וב-Views ביחד או לעבוד ישירות עם ספריית SceneCore. לפרטים נוספים, אפשר לעיין במדריך שלנו לעבודה עם תצוגות.

מידע על תת-מרחבים ורכיבים מרחביים

כשכותבים אפליקציה ל-Android XR, חשוב להבין את המושגים תת-מרחב ורכיבים מרחביים.

מידע על תת-מרחב

כשמפתחים עבור Android XR, צריך להוסיף תת-מרחב לאפליקציה או לפריסה. תת-מרחב הוא חלוקה של מרחב תלת-ממדי בתוך האפליקציה, שבה אפשר למקם תוכן תלת-ממדי, ליצור פריסות תלת-ממדיות ולהוסיף עומק לתוכן דו-ממדי. מרחב משנה מוצג רק כשהמרחב מופעל. במרחב הבית או במכשירים שאינם XR, המערכת מתעלמת מכל קוד שנמצא במרחב המשנה הזה.

יש כמה דרכים ליצור מרחב משנה:

  • Subspace: רכיב ה-Composable הזה יוצר היררכיית ממשק משתמש מרחבי חדשה ועצמאית. הוא לא מקבל בירושה את המיקום המרחבי, הכיוון או קנה המידה של אף רכיב הורה Subspace שהוא מוטמע בתוכו. המערכת קובעת באופן אוטומטי את הגודל של תיבת התוכן המומלצת Subspace.
  • PlanarEmbeddedSubspace: אפשר למקם את רכיב ה-Composable הזה בהיררכיית ממשק המשתמש של האפליקציה, וכך לשמור על פריסות לממשקי משתמש דו-ממדיים ומרחביים. ‫PlanarEmbeddedSubspace מכבד את האילוצים והמיקום של הרכיב ברמה העליונה. אחר כך, התוכן התלת-ממדי שמוצב בתוך האזור הזה ממוקם ביחס לאזור הדו-ממדי שהוגדר.

מידע נוסף זמין במאמר הוספת מרחב משנה לאפליקציה.

מידע על רכיבים מרחביים

רכיבים קומפוזביליים של תת-מרחב: אפשר לעבד את הרכיבים האלה רק בתת-מרחב. צריך להוסיף אותם בין התגים Subspace לפני שמציבים אותם בפריסה דו-ממדית. SubspaceModifier מאפשר להוסיף מאפיינים כמו עומק, היסט ומיקום לרכיבים הניתנים להרכבה של המרחב המשני.

רכיבים מרחביים אחרים לא מחייבים קריאה בתוך תת-מרחב. הם מורכבים מרכיבי דו-ממד רגילים שעטופים בקונטיינר מרחבי. אפשר להשתמש ברכיבים האלה בפריסות דו-ממדיות או תלת-ממדיות אם הם מוגדרים לשני הסוגים. אם לא מפעילים את התכונה 'הוספת מימד מרחבי', המערכת מתעלמת מהתכונות המרחביות שלהם והם חוזרים לתכונות המקבילות הדו-ממדיות.

יצירת לוח מרחבי

SpatialPanel הוא רכיב של מרחב משנה שאפשר להרכיב ממנו תצוגה של תוכן אפליקציה – למשל, אפשר להציג הפעלת וידאו, תמונות סטילס או כל תוכן אחר בחלונית מרחבית.

דוגמה לחלונית ממשק משתמש מרחבי

אפשר להשתמש ב-SubspaceModifier כדי לשנות את הגודל, ההתנהגות והמיקום של החלונית המרחבית, כמו בדוגמה הבאה.

Subspace {
    SpatialPanel(
        SubspaceModifier
            .height(824.dp)
            .width(1400.dp),
        dragPolicy = MovePolicy(),
        resizePolicy = ResizePolicy(),
    ) {
        SpatialPanelContent()
    }
}

@Composable
fun SpatialPanelContent() {
    Box(
        Modifier
            .background(color = Color.Black)
            .height(500.dp)
            .width(500.dp),
        contentAlignment = Alignment.Center
    ) {
        Text(
            text = "Spatial Panel",
            color = Color.White,
            fontSize = 25.sp
        )
    }
}

מידע חשוב על הקוד

  • ממשקי ה-API של SpatialPanel הם קומפוזיציות של תת-מרחבים, ולכן צריך להפעיל אותם בתוך Subspace. התקשרות אליהם מחוץ למרחב משנה תגרום לחריגה.
  • הגודל של SpatialPanel הוגדר באמצעות המפרטים height ו-width ב-SubspaceModifier. אם לא מציינים את המידות האלה, המידות של החלונית נקבעות לפי המידות של התוכן שלה.
  • המשתמש יכול להזיז חלונית על ידי הוספת משנה של מרחב משנה movable.
  • מאפשרים למשתמש לשנות את הגודל של חלונית על ידי הוספת משנה של מרחב משנה resizable.
  • פרטים על שינוי הגודל והמיקום זמינים בהנחיות שלנו לעיצוב חלונית מרחבית. מידע נוסף על הטמעת קוד זמין במאמרי העזרה שלנו.

איך פועל משנה התכונה movable

כשהמשתמשים מרחיקים את החלונית מהם, כברירת מחדל, המקש movable משנה את גודל החלונית באופן דומה לשינוי הגודל של החלוניות על ידי המערכת במרחב הביתי. כל התוכן לילדים יורש את ההתנהגות הזו. כדי להשבית את ההגדרה הזו, צריך להגדיר את הפרמטר shouldScaleWithDistance לערך false.

יצירת לוויין

רכיב Orbiter הוא רכיב מרחבי בממשק המשתמש. הוא מיועד לצירוף לחלונית מרחבית או לרכיב פריסה מרחבית תואם, כמו SpatialColumn, SpatialRow או SpatialBox. בדרך כלל, כרטיס מידע צף מכיל פריטי ניווט ופעולה לפי הקשר שקשורים לישות שאליה הוא מעוגן. לדוגמה, אם יצרתם חלונית תלת-ממדית להצגת תוכן וידאו, תוכלו להוסיף אמצעי בקרה להפעלת וידאו בתוך רכיב מסוג Orbiter.

דוגמה למסלול הקפה

כפי שמוצג בדוגמה הבאה, מפעילים orbiter בתוך פריסת הדו-ממד ב-SpatialPanel כדי לעטוף את אמצעי הבקרה של המשתמש, כמו ניווט. הפעולה הזו תגרום לחילוץ שלהם מפריסת הדו-ממד ולצירוף שלהם לחלונית המרחבית בהתאם להגדרה שלכם.

Subspace {
    SpatialPanel(
        SubspaceModifier
            .height(824.dp)
            .width(1400.dp),
        dragPolicy = MovePolicy(),
        resizePolicy = ResizePolicy(),
    ) {
        SpatialPanelContent()
        OrbiterExample()
    }
}

@Composable
fun OrbiterExample() {
    Orbiter(
        position = ContentEdge.Bottom,
        offset = 96.dp,
        alignment = Alignment.CenterHorizontally
    ) {
        Surface(Modifier.clip(CircleShape)) {
            Row(
                Modifier
                    .background(color = Color.Black)
                    .height(100.dp)
                    .width(600.dp),
                horizontalArrangement = Arrangement.Center,
                verticalAlignment = Alignment.CenterVertically
            ) {
                Text(
                    text = "Orbiter",
                    color = Color.White,
                    fontSize = 50.sp
                )
            }
        }
    }
}

מידע חשוב על הקוד

  • מכיוון שרכיבי ה-UI של ה-Orbiter הם מרחביים, אפשר לעשות שימוש חוזר בקוד בפריסות דו-ממדיות או תלת-ממדיות. בפריסה דו-ממדית, האפליקציה מציגה רק את התוכן בתוך המרכזית ומתעלמת מהמרכזית עצמה.
  • מידע נוסף על אופן השימוש באורביטרים ועל העיצוב שלהם זמין בהנחיות העיצוב שלנו.

הוספת כמה חלוניות מרחביות לפריסה מרחבית

אפשר ליצור כמה חלוניות מרחביות ולמקם אותן בפריסה מרחבית באמצעות SpatialRow, SpatialColumn, SpatialBox ו-SpatialSpacer.

דוגמה לכמה לוחות מרחביים בפריסה מרחבית

בדוגמה הבאה אפשר לראות איך עושים את זה.

Subspace {
    SpatialRow {
        SpatialColumn {
            SpatialPanel(SubspaceModifier.height(250.dp).width(400.dp)) {
                SpatialPanelContent("Top Left")
            }
            SpatialPanel(SubspaceModifier.height(200.dp).width(400.dp)) {
                SpatialPanelContent("Middle Left")
            }
            SpatialPanel(SubspaceModifier.height(250.dp).width(400.dp)) {
                SpatialPanelContent("Bottom Left")
            }
        }
        SpatialColumn {
            SpatialPanel(SubspaceModifier.height(250.dp).width(400.dp)) {
                SpatialPanelContent("Top Right")
            }
            SpatialPanel(SubspaceModifier.height(200.dp).width(400.dp)) {
                SpatialPanelContent("Middle Right")
            }
            SpatialPanel(SubspaceModifier.height(250.dp).width(400.dp)) {
                SpatialPanelContent("Bottom Right")
            }
        }
    }
}

@Composable
fun SpatialPanelContent(text: String) {
    Column(
        Modifier
            .background(color = Color.Black)
            .fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(
            text = "Panel",
            color = Color.White,
            fontSize = 15.sp
        )
        Text(
            text = text,
            color = Color.White,
            fontSize = 25.sp,
            fontWeight = FontWeight.Bold
        )
    }
}

מידע חשוב על הקוד

  • SpatialRow, ‏ SpatialColumn, ‏ SpatialBox ו-SpatialSpacer הם רכיבים של תת-מרחב, ולכן צריך למקם אותם בתוך תת-מרחב.
  • משתמשים בSubspaceModifier כדי להתאים אישית את הפריסה.
  • בפריסות עם כמה חלוניות בשורה, מומלץ להגדיר רדיוס של 825dp באמצעות SubspaceModifier כדי שהחלוניות יקיפו את המשתמש. פרטים נוספים זמינים בהנחיות העיצוב.

הוספת אובייקט תלת-ממדי לפריסה באמצעות SpatialGltfModel

‫Android XR תומך בפורמט glTF למודלים תלת-ממדיים, שבדרך כלל נשמרים כקבצים מסוג .glb. כדי להוסיף את האובייקטים האלה לפריסה, צריך להשתמש בקומפוזיציה SpatialGltfModel. ממשק ה-API הזה מפשט את התהליך של טעינת נכסים וניהול המצב שלהם.

כדי להציג מודל, קודם צריך להגדיר את המקור והמצב שלו באמצעות rememberSpatialGltfModelState. אפשר לטעון מודלים מהתיקייה assets של האפליקציה, מ-URI או מ-raw data.

val modelState = rememberSpatialGltfModelState(
    source = SpatialGltfModelSource.fromPath(
        Paths.get("models/model_name.glb")
    )
)

אחרי שמגדירים את הסטטוס, משתמשים ב-SpatialGltfModel composable כדי להציג אותו ב-Subspace.

SpatialGltfModel(state = modelState, modifier = SubspaceModifier)

מידע חשוב על הקוד

  • טעינה אסינכרונית: המודל נטען באופן אסינכרוני. במהלך ההרכבה הראשונית, הגודל הפנימי שלו עשוי להיות אפס. הפריסה נמדדת מחדש כשהמודל מוכן.
  • שליטה במצב: משתמשים ב-SpatialGltfModelState.status כדי לשלוח שאילתה לגבי סטטוס הטעינה או כדי לשלוט באנימציות.
  • שינוי גודל וקנה מידה: כברירת מחדל, גודל הפריסה תואם לתיבת התוחמת של הנכס. אפשר לשנות את ברירת המחדל ולהשתמש ב-SubspaceModifier.size כדי לשנות את קנה המידה של המודל באופן אחיד כך שיתאים לגבולות שצוינו.

שימוש ב-SceneCoreEntity כדי למקם ישויות בפריסה

רכיב ה-Composable‏ SceneCoreEntity מגשר בין הספריות Jetpack SceneCore ו-Compose for XR, כך שתוכלו להשתמש בישויות שנבנו באמצעות SceneCore בפריסות של Compose. כך אפשר ליצור ישויות ברמה נמוכה יותר ורכיבים מותאמים אישית, ועדיין לאפשר ל-Compose לשנות את הגודל, למקם, להוסיף רכיבים משניים וליישם משנים על הישויות האלה.

Subspace {
    SceneCoreEntity(
        modifier = SubspaceModifier.offset(x = 50.dp),
        factory = {
            SurfaceEntity.create(
                session = session,
                pose = Pose.Identity,
                stereoMode = SurfaceEntity.StereoMode.MONO
            )
        },
        update = { entity ->
            // compose state changes may be applied to the
            // SceneCore entity here.
            entity.stereoMode = SurfaceEntity.StereoMode.SIDE_BY_SIDE
        },
        sizeAdapter =
            SceneCoreEntitySizeAdapter({
                IntSize2d(it.width, it.height)
            }),
    ) {
        // Content here will be children of the SceneCoreEntity
        // in the scene graph.
    }
}

מידע חשוב על הקוד

  • בלוק factory: בבלוק factory מאתחלים את ישות SceneCore הבסיסית.
  • בלוק עדכון: משתמשים בבלוק העדכון כדי לשנות את המאפיינים של הישות בתגובה לשינויים במצב של Compose.
  • התאמת הגודל: רכיב sizeAdapter מעביר את המידות של הישות בחזרה למערכת הפריסה של Compose.

מידע נוסף

הוספת משטח לתוכן של תמונות או סרטונים

SpatialExternalSurface הוא רכיב של מרחב משנה שאפשר להרכיב, שיוצר ומנהל את Surface שבו האפליקציה יכולה לצייר תוכן, כמו תמונה או סרטון. ‫SpatialExternalSurface תומך בתוכן סטריאוסקופי או מונוסקופי.

בדוגמה הזו אפשר לראות איך טוענים סרטון סטריאוסקופי זה לצד זה באמצעות Media3 Exoplayer ו-SpatialExternalSurface:

@OptIn(ExperimentalComposeApi::class)
@Composable
fun SpatialExternalSurfaceContent() {
    val context = LocalContext.current
    Subspace {
        SpatialExternalSurface(
            modifier = SubspaceModifier
                .width(1200.dp) // Default width is 400.dp if no width modifier is specified
                .height(676.dp), // Default height is 400.dp if no height modifier is specified
            // Use StereoMode.Mono, StereoMode.SideBySide, or StereoMode.TopBottom, depending
            // upon which type of content you are rendering: monoscopic content, side-by-side stereo
            // content, or top-bottom stereo content
            stereoMode = StereoMode.SideBySide,
        ) {
            val exoPlayer = remember { ExoPlayer.Builder(context).build() }
            val videoUri = Uri.Builder()
                .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
                // Represents a side-by-side stereo video, where each frame contains a pair of
                // video frames arranged side-by-side. The frame on the left represents the left
                // eye view, and the frame on the right represents the right eye view.
                .path("sbs_video.mp4")
                .build()
            val mediaItem = MediaItem.fromUri(videoUri)

            // onSurfaceCreated is invoked only one time, when the Surface is created
            onSurfaceCreated { surface ->
                exoPlayer.setVideoSurface(surface)
                exoPlayer.setMediaItem(mediaItem)
                exoPlayer.prepare()
                exoPlayer.play()
            }
            // onSurfaceDestroyed is invoked when the SpatialExternalSurface composable and its
            // associated Surface are destroyed
            onSurfaceDestroyed { exoPlayer.release() }
        }
    }
}

מידע חשוב על הקוד

  • מגדירים את StereoMode לערך Mono, SideBySide או TopBottom בהתאם לסוג התוכן שמעבדים:
    • Mono: התמונה או פריים הווידאו מורכבים מתמונה אחת זהה שמוצגת לשתי העיניים.
    • SideBySide: התמונה או פריים הסרטון מכילים זוג תמונות או פריים של סרטון שמוצגים זה לצד זה, כאשר התמונה או הפריים בצד ימין מייצגים את מה שרואים בעין ימין, והתמונה או הפריים בצד שמאל מייצגים את מה שרואים בעין שמאל.
    • TopBottom: התמונה או פריים הווידאו מכילים זוג תמונות או פריים של וידאו שמוערמים אנכית, כאשר התמונה או הפריים העליון מייצגים את התצוגה של העין השמאלית, והתמונה או הפריים התחתון מייצגים את התצוגה של העין הימנית.
  • SpatialExternalSurface תומך רק בפלטפורמות מלבניות.
  • Surface הזה לא מתעד אירועי קלט.
  • אי אפשר לסנכרן שינויים ב-StereoMode עם עיבוד האפליקציה או פענוח הסרטון.
  • אי אפשר להציג את הקומפוזבילי הזה מעל חלוניות אחרות, ולכן לא מומלץ להשתמש ב-MovePolicy אם יש חלוניות אחרות בפריסה.

הוספת פלטפורמה לתוכן וידאו שמוגן על ידי DRM

בנוסף, SpatialExternalSurface תומך בהפעלה של זרמי וידאו שמוגנים על ידי DRM. כדי להפעיל את ההגדרה הזו, צריך ליצור משטח מאובטח שמעבד לזיכרון גרפי מוגן. כך התוכן לא יוקלט במסך ולא תהיה גישה אליו לרכיבי מערכת לא מאובטחים.

כדי ליצור משטח מאובטח, מגדירים את הפרמטר surfaceProtection לערך SurfaceProtection.Protected בקומפוזיציה SpatialExternalSurface. בנוסף, צריך להגדיר את Media3 Exoplayer עם פרטי ה-DRM המתאימים כדי לטפל בהשגת הרישיון משרת הרישיונות.

בדוגמה הבאה מוסבר איך להגדיר את SpatialExternalSurface ואת ExoPlayer להפעלת סטרימינג של וידאו שמוגן באמצעות DRM:

@OptIn(ExperimentalComposeApi::class)
@Composable
fun DrmSpatialVideoPlayer() {
    val context = LocalContext.current
    Subspace {
        SpatialExternalSurface(
            modifier = SubspaceModifier
                .width(1200.dp)
                .height(676.dp),
            stereoMode = StereoMode.SideBySide,
            surfaceProtection = SurfaceProtection.Protected
        ) {
            val exoPlayer = remember { ExoPlayer.Builder(context).build() }

            // Define the URI for your DRM-protected content and license server.
            val videoUri = "https://your-content-provider.com/video.mpd"
            val drmLicenseUrl = "https://your-license-server.com/license"

            // Build a MediaItem with the necessary DRM configuration.
            val mediaItem = MediaItem.Builder()
                .setUri(videoUri)
                .setDrmConfiguration(
                    MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
                        .setLicenseUri(drmLicenseUrl)
                        .build()
                )
                .build()

            onSurfaceCreated { surface ->
                // The created surface is secure and can be used by the player.
                exoPlayer.setVideoSurface(surface)
                exoPlayer.setMediaItem(mediaItem)
                exoPlayer.prepare()
                exoPlayer.play()
            }

            onSurfaceDestroyed { exoPlayer.release() }
        }
    }
}

מידע חשוב על הקוד

  • הגדרת surfaceProtection = SurfaceProtection.Protected on SpatialExternalSurface היא חיונית כדי ש-Surface הבסיסי יגובה על ידי מאגרי נתונים מאובטחים שמתאימים לתוכן DRM.
  • הגדרת DRM: צריך להגדיר את MediaItem עם סכמת ה-DRM (לדוגמה, C.WIDEVINE_UUID) ואת ה-URI של שרת הרישיונות. ‫ExoPlayer משתמש במידע הזה כדי לנהל את סשן ה-DRM.
  • תוכן מאובטח: כשמעבדים תוכן להצגה במשטח מוגן, תוכן הווידאו מפוענח ומוצג בנתיב מאובטח, וכך מתקיימות הדרישות של רישוי התוכן. ההגדרה הזו גם מונעת את הצגת התוכן בצילומי מסך.

הוספת רכיבים אחרים של ממשק משתמש מרחבי

אפשר למקם רכיבים של ממשק משתמש מרחבי בכל מקום בהיררכיית ממשק המשתמש של האפליקציה. אפשר לעשות שימוש חוזר ברכיבים האלה בממשק המשתמש הדו-ממדי, והמאפיינים המרחביים שלהם יהיו גלויים רק כשהיכולות המרחביות מופעלות. כך אפשר להוסיף גובה לתפריטים, לתיבות דו-שיח ולרכיבים אחרים בלי לכתוב את הקוד פעמיים. כדי להבין טוב יותר איך להשתמש ברכיבים האלה, כדאי לעיין בדוגמאות הבאות של ממשקי משתמש מרחביים.

רכיב בממשק המשתמש

כשההפרדה המרחבית מופעלת

בסביבת דו-ממד

SpatialDialog

החלונית תזוז מעט אחורה בעומק z כדי להציג תיבת דו-שיח מוגבהת

המערכת חוזרת לתצוגה דו-ממדית Dialog.

SpatialPopup

החלונית תזוז מעט אחורה בעומק כדי להציג חלון קופץ מוגבה

התצוגה חוזרת לPopup דו-ממדי.

SpatialElevation

אפשר להגדיר את SpatialElevationLevel להוספת גובה.

תוכניות ללא מיקום מרחבי.

SpatialDialog

זו דוגמה לתיבת דו-שיח שנפתחת אחרי השהיה קצרה. כשמשתמשים ב-SpatialDialog, תיבת הדו-שיח מופיעה באותו עומק z כמו החלונית המרחבית, והחלונית נדחקת אחורה ב-125dp כשהמרחביות מופעלת. אפשר להשתמש ב-SpatialDialog גם אם לא מפעילים את האפקט המרחבי. במקרה כזה, SpatialDialog חוזר לגרסת ה-2D שלו, Dialog.

@Composable
fun DelayedDialog() {
    var showDialog by remember { mutableStateOf(false) }
    LaunchedEffect(Unit) {
        delay(3000)
        showDialog = true
    }
    if (showDialog) {
        SpatialDialog(
            onDismissRequest = { showDialog = false },
            SpatialDialogProperties(
                dismissOnBackPress = true
            )
        ) {
            Box(
                Modifier
                    .height(150.dp)
                    .width(150.dp)
            ) {
                Button(onClick = { showDialog = false }) {
                    Text("OK")
                }
            }
        }
    }
}

מידע חשוב על הקוד

יצירת לוחות ופריסות בהתאמה אישית

כדי ליצור לוחות בהתאמה אישית שלא נתמכים ב-Compose for XR, אפשר לעבוד ישירות עם מופעים של PanelEntity ועם גרף הסצנה באמצעות ממשקי ה-API של SceneCore.

הצמדת אורביטרים לחלוניות ולפריסות מרחביות

אפשר להצמיד רכיב orbiter ל-SpatialPanels ולרכיבי פריסה מרחבית שהוגדרו ב-Compose. הפעולה הזו כוללת הצהרה על orbiter בפריסה מרחבית של רכיבים בממשק המשתמש, כמו SpatialRow, ‏ SpatialColumn או SpatialBox. הלוויין מעוגן לרכיב האב שהכי קרוב למיקום שבו הוא הוגדר.

ההתנהגות של הלוויין נקבעת לפי המיקום שבו הוא מוצהר:

  • בפריסה דו-ממדית שמוקפת בתג SpatialPanel (כמו שמוצג בקטע הקוד הקודם), האורביטר מעוגן לתג SpatialPanel.
  • ב-Subspace, האובייקט המסתובב מעוגן לישות האם הקרובה ביותר, שהיא הפריסה המרחבית שבה הוגדר האובייקט המסתובב.

בדוגמה הבאה אפשר לראות איך מעגנים את המעקב אחרי המיקום לשורה מרחבית:

Subspace {
    SpatialRow {
        Orbiter(
            position = ContentEdge.Top,
            offset = 8.dp,
            offsetType = OrbiterOffsetType.InnerEdge,
            shape = SpatialRoundedCornerShape(size = CornerSize(50))
        ) {
            Text(
                "Hello World!",
                style = MaterialTheme.typography.titleMedium,
                modifier = Modifier
                    .background(Color.White)
                    .padding(16.dp)
            )
        }
        SpatialPanel(
            SubspaceModifier
                .height(824.dp)
                .width(1400.dp)
        ) {
            Box(
                modifier = Modifier
                    .background(Color.Red)
            )
        }
        SpatialPanel(
            SubspaceModifier
                .height(824.dp)
                .width(1400.dp)
        ) {
            Box(
                modifier = Modifier
                    .background(Color.Blue)
            )
        }
    }
}

מידע חשוב על הקוד

  • כשמגדירים אובייקט מסוג orbiter מחוץ לפריסה דו-ממדית, הוא מעוגן לישות האם הקרובה ביותר שלו. במקרה כזה, ה-orbiter מעוגן לחלק העליון של SpatialRow שבו הוא מוצהר.
  • פריסות מרחביות כמו SpatialRow,‏ SpatialColumn וSpatialBox כוללות ישויות ללא תוכן שמשויכות אליהן. לכן, רכיב orbiter שמוצהר בפריסה מרחבית מעוגן לפריסה הזו.

עוד באותו הקשר