借助适用于 XR 的 Jetpack Compose,您可以使用熟悉的 Compose 概念(例如行和列)以声明方式构建空间界面和布局。这样,您就可以将现有的 Android 界面扩展到 3D 空间,或构建全新的沉浸式 3D 应用。
如果您要将现有的基于 View 的 Android 应用进行空间化处理,则有几种开发选项。您可以使用互操作性 API、将 Compose 与 View 搭配使用,也可以直接使用 SceneCore 库。如需了解详情,请参阅我们的有关使用 View 的指南。
子空间和空间化组件简介
在为 Android XR 编写应用时,请务必了解子空间和空间化组件的概念。
子空间简介
针对 Android XR 进行开发时,您需要向应用或布局添加子空间。子空间是应用内 3D 空间的一个分区,您可以在其中放置 3D 内容、构建 3D 布局,以及为原本为 2D 的内容添加深度。只有在启用空间化时,系统才会渲染子空间。在主屏幕空间或非 XR 设备上,该子空间中的任何代码都会被忽略。
您可以通过以下两种方式创建子空间:
setSubspaceContent()
:此函数会创建应用级子空间。您可以在主 activity 中调用此方法,方法与使用setContent()
相同。应用级子空间的高度、宽度和深度不受限制,从本质上讲,为空间内容提供了无限的画布。Subspace
:此可组合项可放置在应用界面层次结构中的任何位置,让您能够维护 2D 和空间界面的布局,而不会丢失文件之间的上下文。这样,您就可以更轻松地在 XR 设备和其他设备类型之间共享现有应用架构等内容,而无需通过整个界面树提取状态或重新构建应用。
如需了解详情,请参阅向应用添加子空间。
关于空间化组件
子空间可组合项:这些组件只能在子空间中呈现。必须先将它们封装在 Subspace
或 setSubspaceContent
中,然后才能放置在 2D 布局中。借助 SubspaceModifier
,您可以向子空间可组合项添加深度、偏移和定位等属性。
其他空间化组件不需要在子空间内调用。它们由封装在空间容器中的传统 2D 元素组成。如果为 2D 和 3D 布局都定义了这些元素,则可以在 2D 或 3D 布局中使用这些元素。如果未启用空间化,系统会忽略其空间化地图项,并回退到其 2D 对应项。
创建空间面板
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
API 是子空间可组合项,因此您必须在Subspace
或setSubspaceContent
内调用它们。在子空间之外调用它们会抛出异常。 - 通过添加
movable
或resizable
修饰符,允许用户调整面板大小或移动面板。 - 如需详细了解尺寸和定位,请参阅我们的空间面板设计指南。如需详细了解代码植入,请参阅我们的参考文档。
创建轨道器
轨道器是一种空间界面组件。它旨在附加到相应的空间面板、布局或其他实体。轨道器通常包含与其锚定的实体相关的导航和上下文操作项。例如,如果您创建了用于显示视频内容的空间面板,则可以在绕视器中添加视频播放控件。
如以下示例所示,在 SpatialPanel
的 2D 布局内调用 Orbiter 以封装导航等用户控件。这样做会从您的 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 ) } } } }
代码要点
- 由于轨道器是空间界面组件,因此代码可在 2D 或 3D 布局中重复使用。在 2D 布局中,您的应用仅渲染绕行器内的内容,而忽略绕行器本身。
- 如需详细了解如何使用和设计轨道器,请参阅我们的设计指南。
向空间布局添加多个空间面板
您可以使用 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
自定义布局。 - 对于一行包含多个面板的布局,我们建议使用
SubspaceModifier
将曲线半径设置为 825dp,以便面板环绕用户。如需了解详情,请参阅我们的设计指南。
使用体积将 3D 对象放置在布局中
如需在布局中放置 3D 对象,您需要使用一个名为“体积”的子空间可组合项。以下示例展示了如何执行此操作。
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 } } } } }
代码要点
- 如需更好地了解如何在卷中加载 3D 内容,请参阅向应用添加 3D 模型。
添加其他空间界面组件
空间界面组件可放置在应用界面层次结构中的任意位置。这些元素可在 2D 界面中重复使用,并且只有在启用空间功能时,其空间属性才会显示。这样一来,您无需编写两次代码,即可为菜单、对话框和其他组件添加高度。请参阅以下空间界面示例,更好地了解如何使用这些元素。
界面组件 | 启用空间化时 | 在 2D 环境中 |
---|---|---|
| 面板会在 z 深度上稍微向后推,以显示提升式对话框 | 回退到 2D |
| 面板会在 z 深度上稍微向后推,以显示提升后的弹出式窗口 | 回退到 2D |
|
| 不含空间高度信息的节目。 |
SpatialDialog
以下是一个在短暂延迟后打开的对话框示例。使用 SpatialDialog
时,对话框会显示在与空间面板相同的 z 深度,并且在启用空间化时,面板会向后推 125dp。在未启用空间化的情况下,也可以使用 SpatialDialog
,在这种情况下,SpatialDialog
会回退到其 2D 对应项 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") } } } } }
代码要点
- 这是
SpatialDialog
的示例。使用SpatialPopUp
和SpatialElevation
非常相似。如需了解详情,请参阅我们的 API 参考文档。
创建自定义面板和布局
如需创建 Compose for XR 不支持的自定义面板,您可以使用 SceneCore
API 直接与 PanelEntities
和场景图表进行交互。
将轨道器锚定到空间布局和其他实体
您可以将 Orbiter 锚定到 Compose 中声明的任何实体。这涉及在界面元素(例如 SpatialRow
、SpatialColumn
或 SpatialBox
)的空间布局中声明轨道器。绕转器会锚定到距离您声明它最近的父实体。
轨道器的行为取决于您声明轨道器的位置:
- 在封装在
SpatialPanel
中的 2D 布局中(如上一部分的代码段所示),轨道器会锚定到该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) ) } } }
代码要点
- 在 2D 布局之外声明 Orbiter 时,Orbiter 会锚定到其最近的父实体。在本例中,轨道器会锚定到它所声明的
SpatialRow
的顶部。 SpatialRow
、SpatialColumn
、SpatialBox
等空间布局都与无内容的实体相关联。因此,在空间布局中声明的绕视器会锚定到该布局。