בעזרת Jetpack Compose for XR, אתם יכולים ליצור את ממשק המשתמש המרחבי ואת הפריסה שלכם באופן הצהרתי באמצעות מושגים מוכרים של Compose, כמו שורות ועמודות. כך תוכלו להרחיב את ממשק המשתמש הקיים שלכם ב-Android למרחב תלת-ממדי או ליצור אפליקציות תלת-ממדיות חדשות לגמרי.
אם אתם מוסיפים מרחביות לאפליקציה קיימת שמבוססת על Android Views, יש לכם כמה אפשרויות פיתוח. אפשר להשתמש בממשקי API של יכולת פעולה הדדית, להשתמש ב-Compose וב-Views ביחד או לעבוד ישירות עם ספריית SceneCore. לפרטים נוספים, אפשר לעיין במדריך שלנו לעבודה עם תצוגות.
מידע על תת-מרחבים ורכיבים מרחביים
כשכותבים אפליקציה ל-Android XR, חשוב להבין את המושגים תת-מרחב ורכיבים מרחביים.
מידע על subspace
כשמפתחים לאנדרואיד XR, צריך להוסיף Subspace
לאפליקציה או לפריסה. תת-מרחב הוא חלוקה של מרחב תלת-ממדי בתוך האפליקציה, שבה אפשר למקם תוכן תלת-ממדי, ליצור פריסות תלת-ממדיות ולהוסיף עומק לתוכן דו-ממדי. מרחב משנה מוצג רק כשההגדרה 'הוספת אפקטים מרחביים' מופעלת. במרחב הביתי או במכשירים שאינם XR, המערכת מתעלמת מכל קוד שנמצא במרחב המשנה הזה.
יש שתי דרכים ליצור מרחב משנה:
-
Subspace
: אפשר למקם את הרכיב הזה בכל מקום בהיררכיית ממשק המשתמש של האפליקציה, וכך לשמור על פריסות לממשקי משתמש דו-ממדיים ומרחביים בלי לאבד את ההקשר בין הקבצים. כך קל יותר לשתף דברים כמו ארכיטקטורת אפליקציה קיימת בין XR לבין גורמי צורה אחרים בלי להעביר את המצב דרך כל עץ ממשק המשתמש או לתכנן מחדש את האפליקציה. -
ApplicationSubspace
: הפונקציה הזו יוצרת מרחב משנה ברמת האפליקציה בלבד, והיא צריכה להיות ממוקמת ברמה העליונה ביותר בהיררכיה של ממשק המשתמש המרחבי של האפליקציה. ApplicationSubspace
מעבד תוכן מרחבי עםVolumeConstraints
אופציונלי. בניגוד ל-Subspace
, אי אפשר להטמיע אתApplicationSubspace
בתוךSubspace
אוApplicationSubspace
.
מידע נוסף זמין במאמר הוספת מרחב משנה לאפליקציה.
מידע על רכיבים מרחביים
רכיבים שניתנים להרכבה של תת-מרחב: אפשר לעבד את הרכיבים האלה רק בתת-מרחב.
צריך להוסיף אותם בין התגים Subspace
או setSubspaceContent()
לפני שממקמים אותם בפריסה דו-ממדית. SubspaceModifier
מאפשר להוסיף מאפיינים כמו עומק, היסט ומיקום לרכיבים הניתנים להרכבה של תת-המרחב.
רכיבים מרחביים אחרים לא צריכים להיות מופעלים בתוך תת-מרחב. הם מורכבים מרכיבים דו-ממדיים רגילים שעטופים בקונטיינר מרחבי. אפשר להשתמש ברכיבים האלה בפריסות דו-ממדיות או תלת-ממדיות אם הם מוגדרים לשני הסוגים. אם לא מפעילים את התכונה 'הוספת מימד מרחבי', המערכת מתעלמת מהתכונות המרחביות שלהם ומשתמשת במקביל בתכונות הדו-ממדיות שלהם.
יצירת לוח מרחבי
SpatialPanel
הוא רכיב של מרחב משנה שאפשר להרכיב ממנו תצוגה של תוכן אפליקציה – למשל, אפשר להציג הפעלת וידאו, תמונות סטילס או כל תוכן אחר בחלונית מרחבית.
אפשר להשתמש ב-SubspaceModifier
כדי לשנות את הגודל, ההתנהגות והמיקום של החלונית המרחבית, כמו בדוגמה הבאה.
Subspace { SpatialPanel( SubspaceModifier .height(824.dp) .width(1400.dp) .movable() .resizable() ) { 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
. - פרטים על שינוי הגודל והמיקום זמינים בהנחיות שלנו לעיצוב חלונית מרחבית. מידע נוסף על הטמעת קוד זמין במאמרי העזרה שלנו.
איך פועל משנה של תת-מרחב שניתן להזזה
כשהמשתמשים מרחיקים את החלון מהם, כברירת מחדל, משנה מרחב משנה שניתן להזזה משנה את גודל החלון באופן דומה לשינוי הגודל של החלונות על ידי המערכת במרחב הבית. כל התוכן שמוגדר כתוכן לילדים יורש את ההתנהגות הזו. כדי להשבית את האפשרות הזו, צריך להגדיר את הפרמטר scaleWithDistance
לערך false
.
יצירת לוויין
רכיב Orbiter הוא רכיב ממשק משתמש מרחבי. היא מיועדת לצירוף לחלונית מרחבית, לפריסה או לישות אחרת תואמת. בדרך כלל, רכיב ה-Orbiter מכיל פריטי ניווט ופעולות הקשריות שקשורות לישות שהוא מעוגן אליה. לדוגמה, אם יצרתם חלונית תלת-ממדית להצגת תוכן וידאו, תוכלו להוסיף לחצני הפעלה של סרטונים בתוך כדור.
כפי שמוצג בדוגמה הבאה, מפעילים את ה-orbiter בתוך פריסת הדו-ממד ב-SpatialPanel
כדי לעטוף את אמצעי הבקרה של המשתמש, כמו ניווט. הפעולה הזו תגרום לחילוץ שלהם מפריסת הדו-ממד שלכם ולצירוף שלהם לחלונית המרחבית בהתאם להגדרה שלכם.
Subspace { SpatialPanel( SubspaceModifier .height(824.dp) .width(1400.dp) .movable() .resizable() ) { 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 ומתעלמת מה-orbiter עצמו.
- מידע נוסף על אופן השימוש באורביטרים ועל עיצוב שלהם זמין בהנחיות העיצוב שלנו.
הוספת כמה חלוניות מרחביות לפריסה מרחבית
אפשר ליצור כמה חלוניות מרחביות ולמקם אותן בפריסה מרחבית באמצעות 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 composable שנקרא volume. הנה דוגמה לאופן שבו עושים זאת.
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, ) } } }
@OptIn(ExperimentalSubspaceVolumeApi::class) @Composable fun ObjectInAVolume(show3DObject: Boolean) {
מידע נוסף
- במאמר הוספת מודלים תלת-ממדיים לאפליקציה מוסבר איך לטעון תוכן תלת-ממדי בתוך נפח.
הוספת משטח לתוכן של תמונות או סרטונים
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
עם עיבוד של אפליקציה או פענוח של סרטון. - אי אפשר להציג את הרכיב הזה לפני חלוניות אחרות, ולכן לא מומלץ להשתמש במגדירי מיקום אם יש פריסות אחרות בפריסה.
הוספת פלטפורמה לתוכן וידאו שמוגן על ידי 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() } } } }
נקודות עיקריות לגבי הקוד
- Protected Surface: חשוב להגדיר את
surfaceProtection = SurfaceProtection.Protected
onSpatialExternalSurface
כדי ש-Surface
הבסיסי יגובה על ידי מאגרי נתונים מאובטחים שמתאימים לתוכן DRM. - הגדרת DRM: צריך להגדיר את
MediaItem
עם סכמת ה-DRM (לדוגמה,C.WIDEVINE_UUID
) ואת ה-URI של שרת הרישיונות. ExoPlayer משתמש במידע הזה כדי לנהל את סשן ה-DRM. - תוכן מאובטח: כשמעבדים תוכן להצגה במשטח מוגן, תוכן הווידאו מפוענח ומוצג בנתיב מאובטח, וכך מתקיימות הדרישות של רישוי התוכן. בנוסף, התוכן לא יופיע בצילומי מסך.
הוספת רכיבים אחרים של ממשק משתמש מרחבי
אפשר למקם רכיבי ממשק משתמש מרחביים בכל מקום בהיררכיית ממשק המשתמש של האפליקציה. אפשר לעשות שימוש חוזר ברכיבים האלה בממשק המשתמש הדו-ממדי, והמאפיינים המרחביים שלהם יהיו גלויים רק כשמופעלות יכולות מרחביות. כך אפשר להוסיף הגבהה לתפריטים, לתיבות דו-שיח ולרכיבים אחרים בלי לכתוב את הקוד פעמיים. כדי להבין טוב יותר איך להשתמש ברכיבים האלה, כדאי לעיין בדוגמאות הבאות של ממשקי משתמש מרחביים.
רכיב בממשק המשתמש |
כשמפעילים את האפקט של מיקום במרחב |
בסביבת דו-ממד |
---|---|---|
|
החלונית תזוז מעט אחורה בעומק z כדי להציג תיבת דו-שיח מוגבהת |
המערכת חוזרת לתצוגה דו-ממדית |
|
החלונית תזוז מעט אחורה בעומק כדי להציג חלון קופץ מוגבה |
התצוגה חוזרת ל- |
|
אפשר להגדיר את |
תוכניות ללא גובה מרחבי. |
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") } } } } }
נקודות עיקריות לגבי הקוד
- זו דוגמה ל-
SpatialDialog
. השימוש ב-SpatialPopup
וב-SpatialElevation
דומה מאוד. פרטים נוספים זמינים בהפניית ה-API.
יצירה של פריסות ולוחות בהתאמה אישית
כדי ליצור לוחות מותאמים אישית שלא נתמכים על ידי Compose for XR, אפשר לעבוד ישירות עם מופעים של PanelEntity
ועם גרף הסצנה באמצעות ממשקי ה-API של SceneCore
.
הצמדת רכיבי Orbit לסידורים מרחביים ולפריטים אחרים
אפשר לעגן את האורביטר לכל ישות שמוצהרת ב-Compose. הפעולה הזו כוללת הצהרה על רכיב orbiter בפריסה מרחבית של רכיבי ממשק משתמש כמו SpatialRow
, SpatialColumn
או SpatialBox
. הלוויין מעוגן לישות האב הקרובה ביותר למקום שבו הוא הוגדר.
ההתנהגות של ה-orbiter נקבעת לפי המיקום שבו הוא מוצהר:
- בפריסה דו-ממדית שמוקפת בתג
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 מעוגן לחלק העליון של
SpatialRow
שבו הוא מוצהר. - לפריסות מרחביות כמו
SpatialRow
,SpatialColumn
ו-SpatialBox
משויכים ישויות ללא תוכן. לכן, רכיב orbiter שמוצהר בפריסה מרחבית מעוגן לפריסה הזו.
למידע נוסף
- הוספת מודלים תלת-ממדיים לאפליקציה
- פיתוח ממשק משתמש לאפליקציות שמבוססות על תצוגות ב-Android
- הטמעה של Material Design ל-XR