تطوير واجهة المستخدم باستخدام Jetpack Compose للواقع الممتد

باستخدام Jetpack Compose لتطبيقات الواقع المعزّز، يمكنك إنشاء واجهة المستخدم المكانية و التنسيق بشكل تعريفي باستخدام مفاهيم Compose المألوفة، مثل الصفوف والأعمدة. يتيح لك ذلك توسيع واجهة مستخدم Android الحالية إلى مساحة ثلاثية الأبعاد أو إنشاء تطبيقات ثلاثية الأبعاد غامرة جديدة تمامًا.

إذا كنت بصدد إضافة ميزة "الواقع المكاني" إلى تطبيق حالي يستند إلى Android Views، تتوفّر لك عدة خيارات تطوير. يمكنك استخدام واجهات برمجة التطبيقات للتشغيل التفاعلي أو استخدام Compose وViews معًا أو العمل مباشرةً مع مكتبة SceneCore. يمكنك الاطّلاع على دليل العمل مع المشاهدات للحصول على مزيد من التفاصيل.

لمحة عن المساحات الفرعية والمكونات المخصّصة للعرض في مساحات ثلاثية الأبعاد

عند كتابة تطبيقك لأجهزة Android XR، من المهم فهم مفاهيم المساحة الفرعية والمكوّنات المكانية.

لمحة عن المساحة الفرعية

عند تطوير تطبيقات لنظام Android XR، ستحتاج إلى إضافة مساحة فرعية إلى تطبيقك أو تنسيقه. المساحة الفرعية هي قسم من المساحة الثلاثية الأبعاد داخل تطبيقك حيث يمكنك وضع محتوى ثلاثي الأبعاد وإنشاء تصاميم ثلاثية الأبعاد وإضافة عمق إلى المحتوى ثنائي الأبعاد. لا يتم عرض المساحات الفرعية إلا عند تفعيل ميزة "الصوت المكاني". في "المساحة الرئيسية" أو على الأجهزة غير المزوّدة بتقنية الواقع المعزّز، يتم تجاهل أي رمز ضمن هذا الفضاء الفرعي.

تتوفّر طريقتان لإنشاء مساحة فرعية:

  • setSubspaceContent(): تنشئ هذه الدالة مساحة فرعية على مستوى التطبيق. يمكن استدعاء هذا الإجراء في نشاطك الرئيسي بالطريقة نفسها التي تستخدم بها setContent(). إنّ المساحة الفرعية على مستوى التطبيق غير محدودة من حيث الارتفاع والعرض والعمق، ما يوفر في الأساس مساحة رسم لا نهائية لعرض المحتوى المكاني.
  • Subspace: يمكن وضع هذا المكوّن القابل للتجميع في أي مكان ضمن التسلسل الهرمي لواجهة مستخدم تطبيقك، ما يتيح لك الحفاظ على تنسيقات واجهة المستخدم ثنائية الأبعاد والمكانية بدون فقدان السياق بين الملفات. يسهّل ذلك مشاركة عناصر، مثل بنية التطبيق الحالية، بين الواقع المعزّز وأشكال الأجهزة الأخرى بدون الحاجة إلى رفع الحالة من خلال شجرة واجهة المستخدم بالكامل أو إعادة تصميم تطبيقك.

لمزيد من المعلومات، يُرجى الاطّلاع على إضافة مساحة فرعية إلى تطبيقك.

لمحة عن المكونات المكانية

العناصر القابلة للتجميع في مساحة فرعية: لا يمكن عرض هذه العناصر إلا في مساحة فرعية. يجب وضعها بين 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
        )
    }
}

النقاط الرئيسية حول الرمز

  • بما أنّ واجهات برمجة تطبيقات SpatialPanel هي عناصر قابلة للتجميع في مساحة فرعية، يجب طلبها داخل Subspace أو setSubspaceContent. يؤدي استدعاءها خارج مساحة فرعية إلى طرح استثناء.
  • اسمح للمستخدم بتغيير حجم اللوحة أو نقلها عن طريق إضافة المُعدِّلات movable أو resizable.
  • اطّلِع على إرشادات تصميم اللوحة المكانية للحصول على تفاصيل عن الحجم والموضع. يمكنك الاطّلاع على المستندات المرجعية للحصول على مزيد من التفاصيل حول تنفيذ الرمز.

إنشاء مدار

المسار هو مكوّن لواجهة مستخدِم مكانية. تم تصميمه ليتم إرفاقه بمَعلمة لوحة مكانية أو تنسيق أو كيان آخر ملائم. يحتوي العنصر المداري عادةً على عناصر تنقّل وإجراءات سياقية مرتبطة بالكيان الذي يتم تثبيته عليه. على سبيل المثال، إذا أنشأت لوحة مكانية لعرض محتوى فيديو، يمكنك إضافة عناصر التحكّم في تشغيل الفيديو داخل مسار مداري.

مثال على مركبة مدارية

كما هو موضّح في المثال التالي، يمكنك استدعاء عنصر دوار داخل التنسيق ثنائي الأبعاد في علامة SpatialPanel لتغليف عناصر التحكّم الخاصة بالمستخدم، مثل عناصر التنقّل. يؤدي ذلك إلى استخراجها من التنسيق ثنائي الأبعاد وإرفاقها باللوحة المكانية وفقًا لإعداداتك.

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
                )
            }
        }
    }
}

النقاط الرئيسية حول الرمز

  • بما أنّ العناصر الدوّارة هي مكوّنات لواجهة المستخدم المكانية، يمكن إعادة استخدام الرمز البرمجي في تنسيقات ثنائية أو ثلاثية الأبعاد. في التنسيق ثنائي الأبعاد، يعرض تطبيقك المحتوى داخل المدار فقط ويتجاهل المدار نفسه.
  • اطّلِع على إرشادات التصميم للحصول على مزيد من المعلومات عن كيفية استخدام الأقمار المدارية وتصميمها.

إضافة عدة لوحات مكانية إلى تنسيق مكاني

يمكنك إنشاء عدة لوحات مكانية ووضعها ضمن تنسيق مكاني باستخدام 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
                }
            }
        }
    }
}

النقاط الرئيسية حول الرمز

إضافة مكونات أخرى لواجهة المستخدم المكانية

يمكن وضع مكوّنات واجهة المستخدم المكانية في أيّ مكان في التسلسل الهرمي لواجهة المستخدم في تطبيقك. يمكن إعادة استخدام هذه العناصر في واجهة المستخدم ثنائية الأبعاد، ولن تكون سماتها المكانية مرئية إلا عند تفعيل الإمكانات المكانية. يتيح لك ذلك إضافة تأثير التمويه إلى القوائم وملفات الحوار والمكونات الأخرى بدون الحاجة إلى كتابة الرمز البرمجي مرتين. اطّلِع على الأمثلة التالية على واجهة المستخدم المكانية لفهم كيفية استخدام هذه العناصر بشكل أفضل.

مكوّن واجهة المستخدم

عند تفعيل ميزة "الصوت المكاني"

في بيئة ثنائية الأبعاد

SpatialDialog

سيتم دفع اللوحة للخلف قليلاً في العمق z لعرض مربّع حوار مرتفع

الرجوع إلى العرض الثنائي الأبعاد Dialog

SpatialPopUp

سيتم دفع اللوحة للخلف قليلاً في العمق (z) لعرض نافذة منبثقة مرتفعة.

يعود إلى 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 ورسم المشهد باستخدام واجهات برمجة التطبيقات SceneCore.

ربط المدارات بالتصاميم المكانية والكيانات الأخرى

يمكنك تثبيت مسار مداري بأي عنصر تم الإعلان عنه في Compose. ويشمل ذلك تحديد مدار في تنسيق مكاني لعناصر واجهة المستخدم، مثل SpatialRow أو SpatialColumn أو SpatialBox. يتم تثبيت العنصر المداري بالعنصر الرئيسي الأقرب إلى المكان الذي حدّدته له.

يتم تحديد سلوك المدار حسب المكان الذي تحدّده له:

  • في تنسيق ثنائي الأبعاد مُغلف في SpatialPanel (كما هو موضّح في مقتطف رمز برمجي سابق)، يتم تثبيت العنصر المداري على SpatialPanel.
  • في Subspace، يتم تثبيت العنصر المداري بأقرب عنصر رئيسي، وهو التنسيق المكاني الذي تم الإعلان عن العنصر المداري فيه.

يوضّح المثال التالي كيفية تثبيت مدار حول صف مكاني:

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)
            )
        }
    }
}

النقاط الرئيسية حول الرمز

  • عند تحديد مسار مداري خارج تنسيق ثنائي الأبعاد، يتم تثبيت المسار المداري بأقرب عنصر رئيسي له. في هذه الحالة، يتم تثبيت العنصر المداري في أعلى SpatialRow الذي تمّت الإشارة إليه فيه.
  • تحتوي جميع التنسيقات المكانية، مثل SpatialRow وSpatialColumn وSpatialBox، على عناصر لا تحتوي على محتوى مرتبطة بها. لذلك، يتم تثبيت مدار في تنسيق مكاني على هذا التنسيق.

انظر أيضًا