学习 Android XR 基础知识:第 1 部分 - 模式和空间面板

1. 开始之前

您将学习什么

  • XR 外形设备所实现的独特用户体验。
  • 如何通过使用 Jetpack Compose XR 库提供的可组合项,将应用进行调整以充分利用 Android XR 头戴设备的功能。
  • 如何使用 Compose XR 库提供的 UI 元素。
  • 在哪里可以了解更多关于为 Android XR 构建应用的信息。

这不是什么

您需要什么

您将构建什么

在此 Codelab 中,您将增强一个基本的单屏应用,以通过 Android XR 提供沉浸式用户体验。

起点

最终结果

2. 设置

获取代码

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

打开项目

  • 启动 Android Studio 后,导入项目,仅选择 xr-fundamentals/start 目录。 xr-fundamentals/part1 目录包含解决方案代码,您可以在任何时候卡住或只是想查看完整项目时参考它。

熟悉代码

  • 在 Android Studio 中打开项目后,花一些时间查看起始代码。

3. 学习 XR 概念:模式和空间面板

在此 Codelab 中,您将学习两个 Android XR 概念:模式和空间面板。您还将学习如何将这些概念应用于在 Android XR 设备上运行的应用。

模式

在 Android XR 设备上,应用以两种模式之一运行:主空间模式或全空间模式。

主空间模式

d779257a53898d36.jpeg

在主空间模式下,多个应用并排运行,因此用户可以在应用之间进行多任务处理。Android 应用无需修改即可在主空间模式下运行。

全空间模式

c572cdee69669a23.jpeg

在全空间模式下,每次只有一个应用运行,没有空间边界。所有其他应用都被隐藏。应用必须进行额外的工作才能进入全空间模式,并利用此模式下可用的额外功能。

要了解有关这些模式的更多信息,请参阅主空间和全空间模式

空间面板

空间面板是容器元素,充当 Android XR 应用的基本构建块。

在主空间模式下运行时,您的应用将包含在一个面板中,提供类似于在大屏幕 Android 设备上桌面窗口化的体验。

在全空间模式下运行时,您可以将应用内容分解为一个或多个面板,以提供更沉浸的体验。

要了解有关面板的更多信息,请参阅空间面板

4. 在 Android XR 模拟器中运行应用

在开始为 Android XR 增强应用之前,您可以在 Android XR 模拟器中运行应用,以查看其在主空间模式下的外观。

安装 Android XR 系统映像

  1. 首先,在 Android Studio 中打开 SDK 管理器,如果尚未选择,请选择SDK Platforms选项卡。在 SDK 管理器窗口的右下角,确保选中了Show package details(显示软件包详细信息)框。
  2. 在 Android 14 部分下,安装 Android XR ARM 64 v8aAndroid XR Intel x86_64 模拟器映像。映像只能在与自身具有相同架构(x86/ARM)的机器上运行。

创建 Android XR 虚拟设备

  1. 打开设备管理器后,在窗口左侧的Category(类别)列下选择XR。然后,从列表中选择XR Device硬件配置文件,然后点击Next(下一步)。

7a5f6b9c1766d837.png

  1. 在下一页上,选择您之前安装的系统映像。点击Next(下一步),并选择您想要的任何高级选项,最后通过点击Finish(完成)创建 AVD。
  2. 在您刚刚创建的 AVD 上运行应用。

51a6b3eb02916d74.png

5. 设置依赖项

在开始向应用添加 XR 特定功能之前,您需要添加对 Jetpack Compose for XR 库 androidx.xr.compose:compose 的依赖项,该库包含构建 Android XR 差异化体验所需的所有可组合项。

libs.version.toml

[versions]
...
xrCompose = "1.0.0-alpha04"

[libraries]
...
androidx-xr-compose = { group = "androidx.xr.compose", name = "compose", version.ref = "xrCompose" }

build.gradle.kts(模块:app)

dependencies {
    ...
    implementation(libs.androidx.xr.compose)
    ...
}

更新这些文件后,请务必进行Gradle 同步,以确保依赖项已下载到您的项目中。

6. 进入全空间模式

要使用面板等 XR 功能,应用必须在全空间模式下运行。应用进入全空间模式有两种方式

  • 以编程方式,例如响应应用中的用户交互
  • 在启动时立即通过向应用清单添加指令。

以编程方式进入全空间模式

要以编程方式进入全空间模式,您可以在 UI 中提供辅助功能,让用户控制他们希望以哪种模式使用您的应用。此外,当在您的应用使用上下文中合理时,您也可以进入全空间模式。例如,在开始观看视频内容时进入全空间模式,并在播放完成后退出。

为了简化,这可以通过首先在顶部应用栏中添加一个按钮来切换模式来实现。

  1. com.example.android.xrfundamentals.ui.component 包中创建一个新文件 ToggleSpaceModeButton.kt 并添加以下可组合项

ToggleSpaceModeButton.kt

package com.example.android.xrfundamentals.ui.component

import androidx.annotation.DrawableRes
import androidx.compose.material3.Icon
import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.xr.compose.platform.LocalSpatialCapabilities
import androidx.xr.compose.platform.LocalSpatialConfiguration
import com.example.android.xrfundamentals.R
import com.example.android.xrfundamentals.ui.theme.XRFundamentalsTheme

@Composable
fun ToggleSpaceModeButton(modifier: Modifier = Modifier) {
    val spatialConfiguration = LocalSpatialConfiguration.current

    if (LocalSpatialCapabilities.current.isSpatialUiEnabled) {
        ToggleSpaceModeButton(
            modifier = modifier,
            contentDescription = "Request Home Space mode",
            iconResource = R.drawable.ic_home_space_mode,
            onClick = { spatialConfiguration.requestHomeSpaceMode() }
        )
    } else {
        ToggleSpaceModeButton(
            modifier = modifier,
            contentDescription = "Request Full Space mode",
            iconResource = R.drawable.ic_full_space_mode,
            onClick = { spatialConfiguration.requestFullSpaceMode() }
        )
    }
}

@Composable
fun ToggleSpaceModeButton(
    contentDescription: String,
    @DrawableRes iconResource: Int,
    onClick: () -> Unit,
    modifier: Modifier = Modifier
) {
    FilledTonalIconButton(
        modifier = modifier,
        onClick = onClick
    ) {
        Icon(
            painterResource(iconResource),
            contentDescription
        )
    }
}
  1. 当应用在 XR 设备上运行时,将按钮添加为 TopAppBar 中的操作

XRFundamentalsTopAppBar.kt

import androidx.xr.compose.platform.LocalHasXrSpatialFeature

...

TopAppBar(
    ...,
    actions = {
        // Only show the mode toggle if the device supports spatial UI
        if (LocalHasXrSpatialFeature.current) {
            ToggleSpaceModeButton()
        }
    }
)

现在运行应用。

应用启动时在主空间模式下运行。点击面板右上角的按钮切换到全空间模式。

应用在全空间模式下运行。请注意,最小化/关闭应用的系统 UI 已消失。点击面板右上角的按钮切换回主空间模式。

这些代码片段包含一些值得注意的新 API

  • LocalSpatialConfiguration 是一个组合本地,提供对应用当前空间配置的访问。除了请求更改模式的方法外,这还包括其他信息,例如包含应用的体积大小。
  • LocalSpatialCapabilities 是一个组合本地,可用于确定应用当前可用的空间能力。除了模式(主空间或全空间)外,这还包括空间音频和 3D 内容支持等能力。
  • LocalHasXrSpatialFeature 是一个组合本地,可用于确定应用是否在支持空间 UI 功能的设备上运行。它在底层检查设备是否具有 android.software.xr.immersive 系统功能

在启动时进入全空间模式

要指示操作系统以全空间模式启动 Activity,您可以在相应的 <activity> 元素中包含一个具有以下属性的 <property> 元素。仅当用户不太可能同时使用您的应用和其他应用时才建议这样做。

AndroidManifest.xml

<activity
    android:name=".MainActivity" 
    ... >
    <property
        android:name="android.window.PROPERTY_XR_ACTIVITY_START_MODE"
        android:value="XR_ACTIVITY_START_MODE_FULL_SPACE_MANAGED" />
</activity>

现在,当应用启动时,用户会立即进入全空间模式。

5eec781e78280eda.gif

在继续之前,请从清单中删除上述 <property> 元素,以便应用使用在主空间模式下打开的默认行为。

7. 将 UI 拆分为多个面板

现在您的应用可以进入和退出全空间模式了,是时候更好地利用它了。一个很好的方法是将应用内容拆分为多个面板以填充空间,并(可选地)允许用户根据需要移动和调整这些面板的大小。

将您的应用嵌入子空间

首先,在 XRFundamentalsApp 可组合项中的 Scaffold 可组合项之后添加一个 Subspace 可组合项。子空间是您应用中 3D 空间的一个分区,您可以在其中构建 3D 布局(例如添加空间面板)、放置 3D 模型,并为 2D 内容添加深度。

在非 XR 设备上运行时,Subspace 可组合项的内容永远不会进入 Composition。在 XR 设备上运行时,只有当应用在全空间模式下运行时,内容才会进入 Composition。

XRFundamentalsApp.kt

import androidx.xr.compose.spatial.Subspace

...

HelloAndroidXRTheme {
    Scaffold(...)
    Subspace {
    }
}

现在,运行应用

8969b8fd2a79a669.gif

当您的应用包含 Subspace 可组合项时,只会显示其内容。这意味着当您点击按钮进入全空间模式时,不再显示任何内容。要解决此问题,您将在接下来的几个步骤中添加两个空间面板,一个用于包含主要内容,另一个用于次要内容。

为主要内容添加面板

要在全空间模式下显示主要内容,请在 Subspace 可组合项中添加一个 SpatialPanel

由于这是应用的主面板,您可以将 Scaffold 包含在其中,以保持顶部应用栏中的控件存在。

XRFundamentalsApp.kt

import androidx.xr.compose.subspace.SpatialPanel
import androidx.xr.compose.subspace.layout.SubspaceModifier
import androidx.xr.compose.subspace.layout.height
import androidx.xr.compose.subspace.layout.width

...

Subspace {
    SpatialPanel(
        modifier = SubspaceModifier
            .width(1024.dp)
            .height(800.dp)
    ) {
        Scaffold(
            topBar = { XRFundamentalsTopAppBar() }
        ) { innerPadding ->
            Box(Modifier.padding(innerPadding)) {
                PrimaryCard(
                    modifier = Modifier
                        .padding(16.dp)
                        .verticalScroll(rememberScrollState())
                )
            }
        }
    }
}

默认情况下,SpatialPanel 的大小是根据其包含的内容大小确定的,但也可以通过提供 SubspaceModifier 参数来确定。子空间修饰符类似于修饰符,用于修改空间布局组件,如面板。

因为此空间面板的内容未指定其自身的大小,所以必须为面板提供一个 SubspaceModifier 以使其具有宽度和高度。

c3db4b5ec6f3b39e.gif

为次要内容添加面板

现在您已经让应用在全空间模式下运行并使用面板显示主要内容,是时候将次要内容移动到其自己的面板中了。请注意在空间面板中使用 Surface。如果没有它,次要卡片将没有背景,因为空间面板本身是透明的(Scaffold 可组合项在前面的步骤中处理了这一点)。

XRFundamentalsApp.kt

import androidx.compose.material3.Surface

...

Subspace {
    SpatialPanel() { ... }
    SpatialPanel(
        modifier = SubspaceModifier
            .width(340.dp)
            .height(800.dp)
    ) {
        Surface {
            SecondaryCardList(
                modifier = Modifier
                    .padding(16.dp)
                    .verticalScroll(rememberScrollState())
            )
        }
    }
}

现在再次运行应用程序。乍一看,第二个面板可能没有显示出来,但它实际上是显示的——只是被主面板隐藏了。

34936c6d0d82adb8.gif

将面板排列成一行

与 2D 内容一样,使用行和列有助于并排排列可组合项而不会重叠。在使用面板等空间组件时,您可以使用 SpatialRowSpatialCurvedRowSpatialColumn 可组合项来实现此目的。

XRFundamentalsApp.kt

import androidx.xr.compose.subspace.SpatialCurvedRow

...

Subspace {
    SpatialCurvedRow(
        curveRadius = 825.dp
    ) {
        SpatialPanel(...) { ... }
        SpatialPanel(...) { ... }
    }
}

再运行一次应用程序,您应该会看到面板一个接一个地排列成一行。

因为您使用的是 SpatialCurvedRow 可组合项,所以面板会围绕用户弯曲而不是保持在同一平面(就像 SpatialRow 的情况一样),从而提供更具包围感的体验。

2be1a054de7a8106.png

使面板可调整大小

为了让用户控制应用的外观,您可以使用 resizable 子空间修饰符使面板可调整大小。

默认情况下,可调整大小的面板可以缩小到零或无限扩展,因此您可能需要花时间根据它们将包含的内容设置适当的 minimumSizemaximumSize 参数。

有关 resizable 修饰符支持的所有参数的更多详细信息,请参阅参考文档

XRFundamentalsApp.kt

import androidx.xr.compose.subspace.layout.resizable

...

SpatialPanel(
    modifier = SubspaceModifier
        ...
        .resizable(true)
)

466c1154c0e1fda3.gif

使面板可移动

同样,您可以使用 movable 子空间修饰符使面板可移动。

XRFundamentalsApp.kt

import androidx.xr.compose.subspace.layout.movable

...

SpatialPanel(
    modifier = SubspaceModifier
        ...
        .movable(true)
)

330b6a4d1a688a72.gif

有关 movable 修饰符支持的所有参数的更多详细信息,请参阅参考文档

8. 更新清单

现在您已经为应用添加了空间功能,您需要更新清单以表明您的应用支持这些功能。这让 Google Play 可以将您的应用突出显示为 Android XR 设备的差异化应用。

为此,请在 AndroidManifest.xml 文件中添加以下 <uses-feature> 元素

AndroidManifest.xml

<application ...>
    ...
    <uses-feature android:name="android.software.xr.api.spatial" android:required="false"/>
    ...
</application>

android:required 属性的值设为 false 表示如果该功能可用,应用会使用它,但不会阻止应用分发到没有该功能的设备(例如手机)。这使您可以从移动发布轨道分发到手机和 XR 设备。如果您想使用 XR 专用轨道,请将 android:required 设置为 true

9. 恭喜

要继续学习如何充分利用 Android XR,请查看下一个 Codelab:学习 Android XR 基础知识:第 2 部分 - 轨道器和空间环境,以及以下资源和练习

进一步阅读

挑战

  • 使用 resizablemovable 子空间修饰符的附加参数。
  • 添加更多面板。
  • 使用其他空间组件,例如空间对话框

参考文档