باستخدام Jetpack Compose للواقع الممتد، يمكنك إنشاء واجهة المستخدم المكانية والتصميم بشكل تعريفي باستخدام مفاهيم Compose المألوفة، مثل الصفوف والأعمدة. يتيح لك ذلك توسيع واجهة مستخدم Android الحالية إلى مساحة ثلاثية الأبعاد أو إنشاء تطبيقات ثلاثية الأبعاد غامرة جديدة تمامًا.
إذا كنت بصدد تحويل تطبيق حالي يستند إلى Android Views إلى تطبيق مكاني، تتوفّر لك عدة خيارات تطوير. يمكنك استخدام واجهات برمجة التطبيقات الخاصة بالتوافق أو استخدام Compose وViews معًا أو العمل مباشرةً مع مكتبة SceneCore. يمكنك الاطّلاع على دليلنا حول استخدام طرق العرض لمزيد من التفاصيل.
لمحة عن المساحات الفرعية والمكوّنات المكانية
عند كتابة تطبيقك لأجهزة Android XR، من المهم فهم مفهومَي المساحة الفرعية والمكوّنات المكانية.
لمحة عن المساحة الفرعية
عند تطوير تطبيقات Android XR، عليك إضافة Subspace
إلى تطبيقك أو تصميمك. المساحة الفرعية هي جزء من المساحة الثلاثية الأبعاد داخل تطبيقك، ويمكنك من خلالها وضع محتوى ثلاثي الأبعاد وإنشاء تصاميم ثلاثية الأبعاد وإضافة عمق إلى المحتوى الثنائي الأبعاد. لا يتم عرض مساحة فرعية إلا عند تفعيل ميزة "تحديد الموقع المكاني". في "المساحة المنزلية" أو على الأجهزة غير المتوافقة مع الواقع الممتد، يتم تجاهل أي رمز ضمن تلك المساحة الفرعية.
تتوفّر طريقتان لإنشاء مساحة فرعية:
-
Subspace
: يمكن وضع هذا العنصر القابل للإنشاء في أي مكان ضمن التسلسل الهرمي لواجهة المستخدم في تطبيقك، ما يتيح لك الحفاظ على التصاميم لواجهة المستخدم الثنائية الأبعاد والمكانية بدون فقدان السياق بين الملفات. يسهّل ذلك مشاركة عناصر مثل بنية التطبيق الحالية بين تجارب الواقع الممتد وأشكال الأجهزة الأخرى بدون الحاجة إلى نقل الحالة عبر شجرة واجهة المستخدم بأكملها أو إعادة تصميم بنية تطبيقك. -
ApplicationSubspace
: تنشئ هذه الدالة مساحة فرعية على مستوى التطبيق فقط، ويجب وضعها في أعلى مستوى في التسلسل الهرمي لواجهة المستخدم المكانية لتطبيقك. تعرضApplicationSubspace
المحتوى المكاني معVolumeConstraints
اختياري. على عكسSubspace
، لا يمكن أن يكونApplicationSubspace
متداخلاً معSubspace
أوApplicationSubspace
.
لمزيد من المعلومات، يُرجى الاطّلاع على إضافة مساحة فرعية إلى تطبيقك.
لمحة عن المكوّنات المكانية
عناصر Subspace القابلة للإنشاء: لا يمكن عرض هذه المكوّنات إلا في مساحة فرعية.
يجب أن تكون هذه العناصر محاطة بعلامتَي 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 ) } }
النقاط الرئيسية حول الرمز
- بما أنّ واجهات برمجة التطبيقات
SpatialPanel
هي عناصر قابلة للإنشاء في المساحة الفرعية، يجب استدعاؤها داخلSubspace
. سيؤدي استدعاؤها خارج مساحة فرعية إلى حدوث استثناء. - تم ضبط حجم
SpatialPanel
باستخدام المواصفاتheight
وwidth
علىSubspaceModifier
. يؤدي حذف هذه المواصفات إلى تحديد حجم اللوحة استنادًا إلى قياسات محتواها. - السماح للمستخدم بتغيير حجم اللوحة أو نقلها من خلال إضافة المعدِّل
movable
أوresizable
- يمكنك الاطّلاع على إرشادات تصميم اللوحات المكانية لمعرفة تفاصيل حول تحديد الحجم والموضع. يمكنك الاطّلاع على المستندات المرجعية للحصول على مزيد من التفاصيل حول تنفيذ الرمز.
طريقة عمل أداة تعديل المساحة الفرعية القابلة للنقل
عندما يحرك المستخدم لوحة بعيدًا عنه، يعمل معدِّل المساحة الفرعية القابلة للنقل تلقائيًا على تغيير حجم اللوحة بطريقة مشابهة لطريقة تغيير النظام لحجم اللوحات في المساحة الرئيسية. يتم توريث هذا السلوك لجميع محتوى الأطفال. لإيقاف هذه الميزة، اضبط المَعلمة scaleWithDistance
على false
.
إنشاء قمر اصطناعي
المدار هو أحد مكوّنات واجهة المستخدم المكانية. وهي مصمَّمة ليتم ربطها بلوحة أو تخطيط أو كيان مكاني ذي صلة. يحتوي العنصر الدوّار عادةً على عناصر تنقّل وإجراءات سياقية ذات صلة بالعنصر الذي تم تثبيته عليه. على سبيل المثال، إذا أنشأت لوحة مكانية لعرض محتوى فيديو، يمكنك إضافة عناصر التحكّم في تشغيل الفيديو داخل أداة 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 ) } } } }
النقاط الرئيسية حول الرمز
- بما أنّ العناصر الدوّارة هي مكوّنات واجهة مستخدم مكانية، يمكن إعادة استخدام الرمز البرمجي في التصاميم الثنائية أو الثلاثية الأبعاد. في التصميم الثنائي الأبعاد، يعرض تطبيقك المحتوى داخل أداة العرض الكروية فقط ويتجاهل أداة العرض الكروية نفسها.
- يمكنك الاطّلاع على إرشادات التصميم للحصول على مزيد من المعلومات حول كيفية استخدام وتصميم أدوات العرض الدائري.
إضافة لوحات مكانية متعددة إلى تخطيط مكاني
يمكنك إنشاء لوحات متعددة في المساحة ووضعها ضمن تخطيط مكاني
باستخدام 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
لتخصيص التنسيق. - بالنسبة إلى التصاميم التي تتضمّن لوحات متعدّدة في صف واحد، ننصحك بضبط نصف قطر الانحناء على 825 وحدة بكسل مستقلة الكثافة باستخدام
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, ) } } }
@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
مع عرض التطبيق أو فك ترميز الفيديو. - لا يمكن عرض هذا العنصر القابل للإنشاء أمام اللوحات الأخرى، لذا يجب عدم استخدام أدوات التعديل القابلة للنقل إذا كانت هناك لوحات أخرى في التصميم.
إضافة مساحة عرض لمحتوى الفيديو المحمي بموجب إدارة الحقوق الرقمية
يتيح SpatialExternalSurface
أيضًا تشغيل فيديوهات محمية بنظام إدارة الحقوق الرقمية. لتفعيل هذه الميزة، يجب إنشاء مساحة عرض آمنة يتم عرضها في مخازن مؤقتة للرسومات محمية. يمنع ذلك تسجيل الشاشة أو وصول مكونات النظام غير الآمنة إلى المحتوى.
لإنشاء سطح آمن، اضبط المَعلمة surfaceProtection
على
SurfaceProtection.Protected
في العنصر القابل للإنشاء SpatialExternalSurface
.
بالإضافة إلى ذلك، يجب ضبط Media3 Exoplayer باستخدام معلومات إدارة الحقوق الرقمية المناسبة للتعامل مع عملية الحصول على الترخيص من خادم الترخيص.
يوضّح المثال التالي كيفية ضبط SpatialExternalSurface
وExoPlayer
لتشغيل بث فيديو محمي بنظام إدارة الحقوق الرقمية:
@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
علىSpatialExternalSurface
لكي يتم دعمSurface
الأساسي بمخازن مؤقتة آمنة مناسبة للمحتوى المحمي بموجب إدارة الحقوق الرقمية. - إعداد إدارة الحقوق الرقمية (DRM): يجب ضبط
MediaItem
باستخدام نظام إدارة الحقوق الرقمية (على سبيل المثال،C.WIDEVINE_UUID
) ومعرّف الموارد المنتظم (URI) لخادم الترخيص. يستخدم ExoPlayer هذه المعلومات لإدارة جلسة إدارة الحقوق الرقمية. - المحتوى الآمن: عند العرض على سطح محمي، يتم فك ترميز محتوى الفيديو وعرضه على مسار آمن، ما يساعد في استيفاء متطلبات ترخيص المحتوى. يمنع ذلك أيضًا ظهور المحتوى في لقطات الشاشة.
إضافة مكوّنات أخرى لواجهة المستخدم المكانية
يمكن وضع مكوّنات واجهة المستخدم المكانية في أي مكان في التسلسل الهرمي لواجهة المستخدم في تطبيقك. يمكن إعادة استخدام هذه العناصر في واجهة المستخدم الثنائية الأبعاد، ولن تظهر سماتها المكانية إلا عند تفعيل الإمكانات المكانية. يتيح لك ذلك إضافة ارتفاع إلى القوائم ومربّعات الحوار والمكوّنات الأخرى بدون الحاجة إلى كتابة الرمز مرّتين. اطّلِع على الأمثلة التالية لواجهات المستخدم المكانية لفهم كيفية استخدام هذه العناصر بشكل أفضل.
مكوّن واجهة المستخدم |
عند تفعيل ميزة "التجسيم" |
في بيئة ثنائية الأبعاد |
---|---|---|
|
ستتراجع اللوحة قليلاً في العمق z لعرض مربّع حوار مرتفع |
الرجوع إلى العرض الثنائي الأبعاد |
|
ستتراجع اللوحة قليلاً في العمق z لعرض نافذة منبثقة مرتفعة |
الرجوع إلى |
|
يمكن ضبط |
تعرض هذه السمة العروض التي لا تتضمّن ارتفاعًا مكانيًا. |
SpatialDialog
هذا مثال على مربّع حوار يفتح بعد تأخير قصير. عند استخدام
SpatialDialog
، يظهر مربّع الحوار في عمق z نفسه الذي تظهر فيه اللوحة المكانية، ويتم إرجاع اللوحة بمقدار 125 وحدة بكسل مستقلة الكثافة عند تفعيل ميزة "المواءمة المكانية". يمكن أيضًا استخدام SpatialDialog
عندما لا تكون ميزة "التجسيم" مفعّلة، وفي هذه الحالة، يعود SpatialDialog
إلى نظيره الثنائي الأبعاد، أي 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
متشابه جدًا. يمكنك الاطّلاع على مرجع واجهة برمجة التطبيقات للحصول على مزيد من التفاصيل.
إنشاء لوحات وتنسيقات مخصّصة
لإنشاء لوحات مخصّصة لا تتوافق مع Compose for XR، يمكنك العمل مباشرةً مع مثيلات PanelEntity
ومخطط المشهد باستخدام واجهات برمجة التطبيقات SceneCore
.
ربط الكائنات الدائرة بالخرائط المكانية والعناصر الأخرى
يمكنك تثبيت عنصر مدار في أي عنصر معرَّف في Compose. يتضمّن ذلك تعريف عنصر مدار في تخطيط مكاني لعناصر واجهة المستخدم، مثل 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) ) } } }
النقاط الرئيسية حول الرمز
- عند تعريف عنصر مدار خارج تخطيط ثنائي الأبعاد، يتم ربط العنصر المدار بأقرب عنصر رئيسي. في هذه الحالة، يتم تثبيت العنصر المدار في أعلى
SpatialRow
الذي تم تعريفه فيه. - تحتوي التصاميم المكانية، مثل
SpatialRow
وSpatialColumn
وSpatialBox
، على عناصر مرتبطة بها بدون محتوى. لذلك، يتم تثبيت أي كائن مداري تم تعريفه في تخطيط مكاني على هذا التخطيط.
انظر أيضًا
- إضافة تصاميم ثلاثية الأبعاد إلى تطبيقك
- تطوير واجهة مستخدم للتطبيقات المستندة إلى طرق العرض في Android
- تنفيذ التصميم المتعدد الأبعاد للواقع الممتد