使用 Jetpack Compose for XR 开发界面

借助 Jetpack Compose for XR,您可以使用熟悉的 Compose 概念(例如行和列)以声明方式构建空间界面和布局。这样,您就可以将现有 Android 界面扩展到 3D 空间,或者构建全新的沉浸式 3D 应用。

如果要对基于 Android View 的现有应用进行空间化,有多种开发选项可供选择。您可以使用 Interoperability API,将 Compose 和 View 结合使用,或直接使用 SceneCore 库。如需了解详情,请参阅我们的视图使用指南

子空间和空间化组件简介

当您针对 Android XR 编写应用时,请务必了解子空间和空间化组件的概念。

子空间简介

针对 Android XR 进行开发时,您需要向应用或布局添加子空间。子空间是应用中的 3D 空间分区,您可以在其中放置 3D 内容、构建 3D 布局,并为其他 2D 内容增加深度。只有在启用空间化后,系统才会渲染子空间。在 Home Space 或非 XR 设备上,该子空间内的任何代码都会被忽略。

您可以通过以下两种方式创建子空间:

  • setSubspaceContent():此函数会创建一个应用级子空间。您可以在主 activity 中调用此方法,调用方式与使用 setContent() 相同。应用级子空间在高度、宽度和高度方面没有限制,实质上为空间内容提供了无限的画布。
  • Subspace:此可组合项可放置在应用界面层次结构中的任意位置,可让您维护 2D 界面和空间界面的布局,同时又不会丢失文件之间的上下文。这样,您就可以更轻松地在 XR 和其他外形规格之间共享现有应用架构等内容,而无需通过整个界面树提升状态或重新设计应用架构。

如需了解详情,请参阅为应用添加子空间

空间化组件简介

子空间可组合项:这些组件只能在子空间中呈现。它们必须先包含在 SubspacesetSubspaceContent 中,然后才能放置在 2D 布局中。借助 SubspaceModifier,您可以向子空间可组合项添加深度、偏移和位置等属性。

其他空间化组件不需要在子空间内调用。它们由封装在空间容器中的传统 2D 元素组成。这些元素可在 2D 或 3D 布局中使用(如果已针对这两者进行了定义)。如果未启用空间化,它们的空间化特征将被忽略,并回退到与其对应的 2D 特征。

创建空间面板

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 中的 2D 布局内调用轨道器,以封装导航等用户控件。这样做会从 2D 布局中提取它们,并根据您的配置将它们附加到空间面板。

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

@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 布局中,您的应用仅渲染轨道器内部的内容,而忽略轨道器本身。
  • 如需详细了解如何使用和设计轨道器,请参阅我们的设计指南

向空间布局添加多个空间面板

您可以使用 SpatialRowSpatialColumnSpatialBoxSpatialLayoutSpacer 创建多个空间面板并将其放置在空间布局中。

空间布局中的多个空间面板的示例

以下代码示例展示了如何执行此操作。

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

代码要点

使用体积在布局中放置 3D 对象

如需在布局中放置 3D 对象,您需要使用一个名为“Volume”的子空间可组合项。以下示例说明了如何执行此操作。

布局中的 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) {

代码要点

添加其他空间界面组件

空间界面组件可以放置在应用界面层次结构中的任意位置。这些元素可以在 2D 界面中重复使用,只有在启用空间功能后,它们的空间属性才会显示。这样一来,您无需编写两次代码,即可为菜单、对话框和其他组件添加高度。请参阅以下空间界面示例,更好地了解如何使用这些元素。

界面组件

启用空间化后

在 2D 环境中

SpatialDialog

面板将会沿 z 轴深度稍向后推,以显示提升的对话框

回退到 2D Dialog

SpatialPopup

面板将会沿 z 轴深度稍向后推,以显示一个凸起的弹出式内容

回退到 2D Popup

SpatialElevation

可设置 SpatialElevationLevel 以添加高度。

不显示空间高度。

空间对话框

这是在短暂延迟后打开的对话框示例。使用 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")
                }
            }
        }
    }
}

代码要点

创建自定义面板和布局

如需创建 Compose for XR 不支持的自定义面板,您可以使用 SceneCore API 直接使用 PanelEntities 和场景图。

将轨道器锚定到空间布局和其他实体

您可以将轨道器锚定到 Compose 中声明的任何实体。这涉及在界面元素的空间布局(例如 SpatialRowSpatialColumnSpatialBox)中声明轨道器。轨道器会锚定到离您声明位置最近的父实体。

轨道器的行为取决于您声明的位置:

  • 在封装在 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.h2,
                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 布局之外声明轨道器时,轨道器会锚定到其最近的父实体。在本例中,轨道器会锚定到声明它的 SpatialRow 的顶部。
  • SpatialRowSpatialColumnSpatialBox 等空间布局都有与之相关联的无内容实体。因此,空间布局中声明的轨道器会锚定到该布局。

另请参阅