פיתוח ממשק משתמש באמצעות Jetpack Compose ל-XR

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

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

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

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

מידע על מרחב משנה

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

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

  • setSubspaceContent(): הפונקציה הזו יוצרת מרחב משנה ברמת האפליקציה. אפשר להפעיל את הפונקציה הזו בפעילות הראשית באותו אופן שבו משתמשים ב-setContent(). המרחב המשנה ברמת האפליקציה לא מוגבל לגובה, לרוחב ולעומק, ובעצם מספק קנבס אינסופי לתוכן מרחבי.
  • Subspace: אפשר למקם את הרכיב הזה בכל מקום בהיררכיית ממשק המשתמש של האפליקציה, וכך לשמור על פריסות לממשק משתמש דו-ממדי ומרחבי בלי לאבד את ההקשר בין הקבצים. כך קל יותר לשתף דברים כמו ארכיטקטורת אפליקציה קיימת בין XR לבין גורמי צורה אחרים, בלי שתצטרכו להעביר את המצב דרך כל עץ ממשק המשתמש או לתכנן מחדש את האפליקציה.

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

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

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

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

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

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

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

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

Subspace {
   SpatialPanel(
        SubspaceModifier
           .height(824.dp)
           .width(1400.dp)
           .movable()
           .resizable()
           ) {
          SpatialPanelContent()
      }
}

// 2D content placed within the spatial panel
@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
        )
    }
}

נקודות עיקריות לגבי הקוד

יצירת כלי ניווט במסלול

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

דוגמה למכשיר אווירודינמי

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

setContent {
    Subspace {
        SpatialPanel(
            SubspaceModifier
                .height(824.dp)
                .width(1400.dp)
                .movable()
                .resizable()
        ) {
            SpatialPanelContent()
            OrbiterExample()
        }
    }
}

//2D content inside Orbiter
@Composable
fun OrbiterExample() {
    Orbiter(
        position = OrbiterEdge.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
                )
            }
        }
    }
}

נקודות עיקריות לגבי הקוד

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

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

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

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

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

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 ו-SpatialLayoutSpacer הם רכיבים שאפשר ליצור מהם מרחבים משניים, וצריך למקם אותם במרחב משני.
  • משתמשים ב-SubspaceModifier כדי להתאים אישית את הפריסה.
  • בפריסות עם כמה פאנלים בשורה, מומלץ להגדיר רדיוס עקומה של 825dp באמצעות SubspaceModifier כדי שהפאנלים יקיפו את המשתמש. לפרטים נוספים, אפשר לעיין בהנחיות לעיצוב.

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

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

דוגמה לאובייקט תלת-ממדי בפריסה

Subspace {
    SpatialPanel(
        SubspaceModifier.height(1500.dp).width(1500.dp)
            .resizable().movable()
    ) {
        ObjectInAVolume(true)
            Box(
                Modifier.fillMaxSize(),
                contentAlignment = Alignment.Center
            ) {
                Text(
                    text = "Welcome",
                    fontSize = 50.sp,
                )
            }
        }
    }
}

@Composable
fun ObjectInAVolume(show3DObject: Boolean) {
    val xrCoreSession = checkNotNull(LocalSession.current)
    val scope = rememberCoroutineScope()
    if (show3DObject) {
        Subspace {
            Volume(
                modifier = SubspaceModifier
                    .offset(volumeXOffset, volumeYOffset, volumeZOffset) //
Relative position
                    .scale(1.2f) // Scale to 120% of the size

            ) { parent ->
                scope.launch {
                   // Load your 3D Object here
                }
            }
        }
    }
}

נקודות עיקריות לגבי הקוד

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

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

רכיב UI

כשהמיקום מופעל

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

SpatialDialog

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

מעבר לתצוגה דו-ממדית Dialog.

SpatialPopUp

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

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

SpatialElevation

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

תוכניות ללא תצוגה מרחבית.

SpatialDialog

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

@Composable
fun DelayedDialog() {
   var showDialog by remember { mutableStateOf(false) }
   LaunchedEffect(Unit) {
       Handler(Looper.getMainLooper()).postDelayed({
           showDialog = true
       }, 3000)
   }
   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, אפשר לעבוד ישירות עם PanelEntities ועם תרשים הסצינה באמצעות ממשקי ה-API של SceneCore.

עיגון של רכיבי מעקב אחר תנועה למיקומים במרחב ולישויות אחרות

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

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

הדוגמה הבאה מראה איך לקשר כלי ניווט למסגרת מרובע במרחב:

Subspace {
    SpatialRow {
        Orbiter(
            position = OrbiterEdge.Top,
            offset = EdgeOffset.inner(8.dp),
            shape = SpatialRoundedCornerShape(size = CornerSize(50))
        ) {
            Text(
                "Hello World!",
                style = MaterialTheme.typography.titleLarge,
                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 יאוחז בחלק העליון של ה-SpatialRow שבו הוא הוצהר.
  • לכל פריסות מרחביות כמו SpatialRow,‏ SpatialColumn ו-SpatialBox משויכות ישויות ללא תוכן. לכן, אובייקט מסלול שמוצהר בפריסה מרחבית מקובע לפריסה הזו.

למידע נוסף