学习 Android XR 基础知识:第 2 部分 - 轨道器和空间环境

1. 准备工作

学习内容

  • Android XR 能够提供的独特用户体验。
  • 如何使用 Jetpack Compose XR 库针对 Android XR 头戴设备优化应用。
  • 如何使用 Jetpack Compose XR 库中的界面元素。
  • 详细了解如何构建适用于 Android XR 的应用。

这并非是

所需条件

构建内容

在此 Codelab 中,您将通过添加浮动界面元素并自定义用户在使用应用时所处的虚拟环境,进一步利用一些现有的 XR 功能来优化应用。

起始代码

最终结果

2. 进行设置

获取代码

  1. 此 Codelab 的代码可以在 xr-codelabs GitHub 存储库的 xr-fundamentals 目录中找到。如需克隆此代码库,请运行以下命令:
git clone https://github.com/android/xr-codelabs.git
  1. 或者,您也可以下载代码库 Zip 文件。

打开项目

  • 在启动 Android Studio 后,导入 xr-fundamentals/part1 目录。xr-fundamentals/part2 目录包含解决方案代码,如果您遇到困难或只想查看完整项目,可以随时参考。

熟悉代码

  • 在 Android Studio 中打开项目后,花些时间浏览起始代码。
  • 如果您尚未完成第一个 Codelab 或使用 Android XR 模拟器,请按照在 Android XR 模拟器中运行应用中的步骤运行应用。

3. 了解 XR 概念:轨道器

轨道器是在 Full Space 模式下提供的浮动界面元素,通常用于控制空间面板轨道锚定的其他实体中的内容。使用轨道器控制内容可为内容提供更多空间,这意味着用户可在主要内容保持可见的情况下快速访问轨道器包含的功能。借助轨道器,您可以灵活地集成现有界面组件(例如导航栏)或创建新的界面组件。

此外,Orbiter API 还允许您在 Home Space 模式下或在非 XR 设备上运行时照常呈现轨道器的内容,并在 Full Space 模式下自动将其分解为轨道器。

在 Home Space 模式下,此导航栏会在主应用面板中呈现。

在 Full Space 模式下,导航栏会分解为附加到主面板的轨道器。

此时,应用在顶部应用栏中包含一个按钮,用于在 Home Space 模式和 Full Space 模式之间切换。此按钮是 Full Space 模式下运行的控件在轨道器中包含的完美示例,因为将控件移动到主面板的轨道上有助于突出显示该控件,同时也能直观地表明该控件在被点击时会将应用内容收起到该面板中。

当前状态

您将实现的内容

如需详细了解轨道器的设计注意事项,请参阅空间界面

4. 添加轨道器

封装空间模式切换按钮

如需将空间模式切换开关转换为轨道器,请将 ToggleSpaceModeButton 可组合项封装在 Orbiter 可组合项中。

ui/component/XRFundamentalsTopAppBar .kt

import androidx.compose.foundation.shape.CornerSize
import androidx.compose.ui.Alignment
import androidx.compose.ui.unit.dp
import androidx.xr.compose.spatial.EdgeOffset
import androidx.xr.compose.spatial.Orbiter
import androidx.xr.compose.spatial.OrbiterEdge
import androidx.xr.compose.subspace.layout.SpatialRoundedCornerShape

... 

Orbiter(
    position = OrbiterEdge.Top,
    alignment = Alignment.End,
    offset = EdgeOffset.inner(16.dp),
    shape = SpatialRoundedCornerShape(
        CornerSize(percent = 100)
    )
) {
    ToggleSpaceModeButton()
}

现在,运行应用:在 Home Space 模式下运行时,您会发现没有任何变化。不过,当您点击切换开关并让应用进入 Full Space 模式时,您会发现该按钮不再位于顶部应用栏中,而是位于主要空间面板的右上角边缘。

轨道器锚定到主空间面板,因为这是界面树中的最近的父空间实体。轨道器相对于主空间面板的确切位置由 positionalignmentoffset 参数确定。请尝试修改这些参数,看看它们支持的行为范围。

Home Space 模式

Full Space 模式

5. 了解 XR 概念:空间环境

使用 Orbiter 自定义界面元素在 3D 空间中的位置,是提升 XR 设备上的用户体验的绝佳方式。您可以通过自定义用户在使用应用时所处的空间环境,进一步提升用户体验。

空间环境可以整合深度、纹理和 3D 几何形状资源,打造丰富的视觉沉浸式体验。这可通过使用球形天空盒图片(EXR 格式)来提供远处的全景背景和/或几何形状资源(glTF 格式)来提供可混合到天空盒中的前景和中景元素来实现。例如,视频流应用可以使用夜间天空盒,其中包含一个带投影屏幕和汽车的露天影院的 glTF。在为用户设置空间环境时创建资源时,请确保资源达到高分辨率,同时保持合理的文件大小。如需了解详情,请参阅优化环境资源

此外,还可以控制空间环境的不透明度。这样一来,真实世界的视频流就可以穿过并与虚拟环境融合,从而帮助用户保持方向感

一个人站在岩石空间环境中,中间有一个大型界面面板。

在下一步中,您将向应用添加几何形状资源,并创建一个菜单,以便用户选择环境。

如需详细了解如何设计和实现空间环境,请参阅空间环境向应用添加空间环境

6. 让用户更改空间环境

应用如何控制空间环境

在开始之前,最好先了解应用能够精确控制空间环境的方式。

与面板中的内容不同,应用不会直接控制环境。相反,他们可以与 SceneCore 会话进行交互,为他们希望系统使用的环境提供偏好设置。此偏好设置由 SpatialEnvironmentPreference 表示,其中包含天空盒 EXR 图像和/或几何 gLTF。当应用提供偏好设置时,会发生什么取决于应用在设置偏好设置时的功能。如果您的应用具有更改环境的功能,系统会立即使用该功能。如果没有,则在您的应用获得该功能时,系统会应用该偏好设置。

例如,应用在 Home Space 模式下运行时通常无法更改环境,但在 Full Space 模式下运行时通常可以更改环境。因此,如果您允许用户在 Home Space 模式下设置环境偏好设置,则该偏好设置通常在应用以 Full Space 模式运行时才会生效。

添加对 XR SceneCore 库的依赖项

如需开始修改空间环境,请添加对 XR SceneCore 库的依赖项,您将使用该库来加载环境资源并设置环境偏好设置。您还需要添加对 kotlinx-coroutines-guava 制品的依赖项,因为用于加载资源的一些 API 使用 ListenableFuture 数据类型。

libs.version.toml

[versions]
...
xrSceneCore = "1.0.0-alpha04"
kotlinxCoroutinesGuava = "1.10.2"

[libraries]
...
androidx-xr-scenecore = { group = "androidx.xr.scenecore", name = "scenecore", version.ref = "xrSceneCore"}
jetbrains-kotlinx-coroutines-guava = {group = "org.jetbrains.kotlinx", name="kotlinx-coroutines-guava", version.ref = "kotlinxCoroutinesGuava"}

app/build.gradle.kts

dependencies {
    ...
    implementation(libs.androidx.xr.scenecore)
    implementation(libs.jetbrains.kotlinx.coroutines.guava)
    ...
}

将环境资源添加到项目中

如需指定唯一的环境偏好设置,您需要一个天空盒和/或几何形状资源。在此 Codelab 中,您将仅使用 green_hills_ktx2_mipmap.glb 几何形状资源,您可在包含解决方案代码的 part2 文件夹中或 GitHub 上找到该资源。

  1. 在 Android Studio 的“Project”窗口中,右键点击应用模块。随后,依次选择 New > Folder > Assets Folder 并点击 Finish 以创建文件夹。
  2. GLB 文件添加到您刚刚创建的 app/src/main/assets 文件夹中。

对环境选项进行建模

为了简化界面代码与系统 API 之间的交互,您可以创建 Kotlin 数据类来对每个环境选项进行建模。

  1. 右键点击“Project”窗口中的 com.example.android.xrfundamentals 软件包,然后依次选择 New > Package。输入 com.example.android.xrfundamentals.environment 作为软件包名称。
  2. 右键点击该软件包,然后依次选择 New > Kotlin Class/File。输入 EnvironmentOption 作为名称,然后点击 Data class 类型。
  3. 在您刚刚创建的文件中添加以下代码:

EnvironmentOption.kt

data class EnvironmentOption(val name: String, val skyboxPath: String?, val geometryPath: String?)

val DEFAULT_ENVIRONMENT = EnvironmentOption("Default", null, null)

val ENVIRONMENT_OPTIONS = listOf(
    DEFAULT_ENVIRONMENT,
    EnvironmentOption("Green Hills", null, "green_hills_ktx2_mipmap.glb")
)

添加一个辅助程序以用于创建加载资源并返回 SpatialEnvironmentPreference

接下来,您可向数据类添加一个辅助方法,以便轻松将 EnvironmentOption 转换为相应的 SpatialEnvrionmentPreference

EnvironmentOption.kt

import androidx.xr.runtime.Session
import androidx.xr.scenecore.ExrImage
import androidx.xr.scenecore.GltfModel
import androidx.xr.scenecore.SpatialEnvironment
import kotlinx.coroutines.guava.await

...

data class EnvironmentOption(val name: String, val skyboxPath: String?, val geometryPath: String?) {
    suspend fun toSpatialEnvironmentPreference(session: Session): SpatialEnvironmentPreference? {
        if (skyboxPath == null && geometryPath == null) {
            return null
        } else {
            val skybox = skyboxPath?.let {
                ExrImage.create(session, it).await()
            }

            val geometry = geometryPath?.let {
                GltfModel.create(session, it).await()
            }

            return SpatialEnvironmentPreference(skybox, geometry)
        }
    }
}

这里有一些注意事项:

  • 如果天空盒和几何图形均为 null,则返回 null,以指明应使用默认系统环境偏好设置。如需了解详情,请参阅 setSpatialEnvironmentPreference
  • skyboxgeometry 资源是异步创建的,因为这些资源通常很大,需要花费一些时间才能读入内存。在正式版应用中,如果频繁切换环境,您可能需要考虑将这些资源缓存在内存中。

实现环境选择界面

如需实现界面,您需要添加第二个轨道器,该轨道器会在用户点击时旋转环境选项。

添加轨道器

  1. 右键点击“Project”窗口中的 app 模块,然后依次选择 New > Vector Asset。点击 Clip art 字段,然后搜索并选择 landscape 资源(从已填充图标系列中),接着点击OKNext以创建资源。
  2. 右键点击 com.example.android.xrfundamentals.ui.component 软件包,然后依次选择 New > Kotlin Class/File。输入 EnvironmentSelectionOrbiter 作为名称,然后点击File类型。
  3. 在您刚刚创建的文件中,添加以下 EnvironmentSelectionOrbiter 可组合项的实现方案

EnvironmentSelectionOrbiter.kt

import androidx.compose.foundation.shape.CornerSize
import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.xr.compose.spatial.EdgeOffset
import androidx.xr.compose.spatial.Orbiter
import androidx.xr.compose.spatial.OrbiterEdge
import androidx.xr.compose.subspace.layout.SpatialRoundedCornerShape
import com.example.android.xrfundamentals.R

@Composable
fun EnvironmentSelectionOrbiter(
    modifier: Modifier = Modifier,
    onClick: () -> Unit = {},
) {
    Orbiter(
        position = OrbiterEdge.Top,
        alignment = Alignment.Start,
        offset = EdgeOffset.inner(16.dp),
        shape = SpatialRoundedCornerShape(
            CornerSize(100)
        )
    ) {
        FilledTonalIconButton(
            modifier = modifier,
            onClick = onClick,
        ) {
            Icon(painterResource(R.drawable.baseline_landscape_24), "Show environment selection dialog")
        }
    }
}
  1. 最后,在主要空间面板中添加 EnvironmentSelectionOrbiter

XRFundamentalsApp.kt

import androidx.xr.compose.platform.LocalSpatialCapabilities
import com.example.android.xrfundamentals.ui.component.EnvironmentSelectionOrbiter

...

SpatialPanel(...) {

    // Only show the environment selection orbiter if the app is actually able to
    // change the environment
    if (LocalSpatialCapabilities.current.isAppEnvironmentEnabled) {
        EnvironmentSelectionOrbiter(
            onClick = { TODO() }
        )
    }
    ...
}

在轨道器被点击后更改环境

为了让所有内容正常运行,还需要执行最后一步,即在 EnvironmentSelectionOrbiter 点击处理程序中调用 setSpatialEnvironmentPreference

  1. 设置一个变量来跟踪当前环境选项(在 Subspace 之外,以便在 Home Space 模式和 Full Space 模式之间切换时保持状态)。此外,还要为当前 XR 会话创建变量,并创建一个协程作用域来调用 toSpatialEnvironmentPreference 辅助程序

XRFundamentalsApp.kt

import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.xr.compose.platform.LocalSession

... 

var currentEnvironmentOptionIndex by remember { mutableStateOf(0) }

Subspace {
    val session = checkNotNull(LocalSession.current)
    val scope = rememberCoroutineScope()
    ...
}
  1. 实现 onClick 回调以轮询环境选项。

XRFundamentalsApp.kt

EnvironmentSelectionOrbiter(
    onClick = {
        scope.launch {
            currentEnvironmentOptionIndex =
                (currentEnvironmentOptionIndex + 1) % ENVIRONMENT_OPTIONS.size
            session.scene.spatialEnvironment.setSpatialEnvironmentPreference(
                ENVIRONMENT_OPTIONS[currentEnvironmentOptionIndex].toSpatialEnvironmentPreference(
                    session
                )
            )
        }
    }
)

再次运行应用,看看您是否可以在 Green Hills 环境和默认环境之间切换!

b0e9571ef5f5597b.gif

7. 恭喜

如需继续了解如何充分利用 XR,请查看以下资源和练习:

深入阅读

挑战

  • 查找或创建其他环境资源,并将其添加为选项。
  • 修改环境控制器和界面,以允许用户使用 setPassthroughOpacityPreference API 设置传递偏好设置。请注意,控制透传功能时所用的机制不同于更改环境资源时所用的机制。

参考文档