操作指南

使用 Jetpack XR SDK 将 Androidify 引入 XR

阅读用时 9 分钟
2025 年 10 月 22 日
查看 Dereck Bridie 的个人资料
Dereck Bridie 开发者关系工程师

Samsung Galaxy XR 现已上市,搭载 Android XR!这篇博文是我们 Android XR Spotlight Week 系列博文的一部分,旨在提供各种资源(博文、视频、示例代码等),帮助您学习、构建应用并为 Android XR 做好准备。

随着 Samsung Galaxy XR 的推出,首款搭载 Android XR 的设备正式上市。现在,用户可以在全新的维度(三维)中畅享 Play 商店中的许多常用应用!

三维空间非常宽敞,您的应用也有充足的空间。立即开始使用适合您应用的工具。例如,您可以使用Jetpack XR SDK,借助 Kotlin 和 Compose 等现代 Android 开发工具构建沉浸式 XR 体验。

在这篇博文中,我们将介绍我们如何将备受喜爱的 Androidify 应用 的奇思妙想引入 XR,并介绍将应用引入 XR 所需的基础知识。

Androidify 概览

Androidify 是一款开源应用,可让您使用 Gemini、CameraX、Navigation 3 等最新技术以及 Jetpack Compose 创建 Android 机器人。Androidify 最初旨在通过创建 自适应布局,在手机、可折叠设备和平板电脑上呈现出色的效果。

customize.png

Androidify 在多种设备形态上都能呈现出色的效果

自适应布局的一个关键要素是可重复使用的可组合项。Jetpack Compose 可帮助您创建小巧的界面组件,这些组件可以采用不同的方式布局,从而打造直观的用户体验,无论用户使用的是哪种类型的设备。事实上,Androidify 与 Android XR 兼容,无需对应用进行任何修改!

customize_2.png

Androidify 使用大屏设备自适应布局来适应 XR,无需更改代码

对于没有针对 Android XR 进行特殊处理的应用,可以在大小合适的窗口中执行多任务处理,并且其运行方式与在大屏设备上非常相似。因此,Androidify 在 Android XR 上已具备完整的功能,无需进行额外的工作!但我们并不满足于此,因此决定再接再厉,打造一款 XR 差异化应用,为 XR 用户带来愉悦的体验。

了解 XR

我们先来了解 Android XR 的关键基本概念,首先是应用可以运行的两种模式:主共享空间和全沉浸空间。

homespace.png
主共享空间中的应用
homespace2.png
全沉浸空间中的应用

主共享空间中,多个应用可以并排运行,以便用户在不同的窗口之间同时处理多个任务。从这个意义上讲,它很像大屏 Android 设备上的窗口化模式,但位于虚拟空间中!

全沉浸空间中,应用没有空间边界,可以充分利用 Android XR 的空间功能,例如空间界面和控制虚拟环境。

虽然让应用仅在全沉浸空间中运行可能很诱人,但用户可能希望使用您的应用执行多任务处理,因此同时支持这两种模式可以提升用户体验。

为 Androidify 的新维度设计

愉悦的应用始于出色的设计。Android DevRel 的高级设计倡导者 Ivy Knight 负责处理 Androidify 的现有设计,并为 XR 设计新方案。Ivy,请开始吧!

为 XR 设计需要采用独特的方法,但实际上与移动设计有很多共同之处。我们首先考虑的是包含:如何在子空间中组织和分组界面元素,方法是明确显示边界或巧妙地暗示边界。我们还学会了接受各种大小的空间界面元素,这些元素旨在根据用户做出调整和移动。与 Androidify 一样,使用自适应布局进行构建,以便将布局分解为空间界面的各个部分。

从主共享空间开始设计

幸运的是,Android XR 可让您从当前的主共享空间应用开始,因此我们只需添加窗口工具栏和全沉浸空间过渡按钮,即可过渡到扩展的 XR 设计。

我们还考虑了可能的硬件功能以及用户与这些功能的互动方式。Androidify 的移动布局可以适应各种姿势、班级规模和摄像头数量,从而提供更多照片选项。按照此模型,我们还必须调整头戴设备的摄像头布局。我们还需要针对文本进行调整,以考虑界面与用户的距离

为向全沉浸空间的大幅转变设计

全沉浸空间是最大的转变,但它为我们提供了最大的创意空间来调整设计。

tablet_to_xr.webp
从平板电脑到 XR

Androidify 使用视觉包含(即窗格)将具有背景和轮廓的功能分组,例如“拍摄或选择照片”窗格。我们还使用了顶部应用栏等组件,通过为其他窗格添加边框来创建自然包含。最后,某些元素之间的距离暗示了内在包含,例如“开始转换”底部按钮,该按钮靠近“选择我的机器人颜色”窗格。

空间面板便于分离。如需确定如何调整移动设计以适应空间面板,请尝试移除表面,从最靠后的表面开始,然后向前移动。查看可以移除多少背景以及剩余的内容。在为 Androidify 完成此练习后,剩余的是一个大型绿色 Android 曲线。该曲线不仅充当品牌宣传时刻和背景,还充当三维空间中内容的锚点。

建立此锚点不仅可以更轻松地想象元素如何围绕它移动,还可以更轻松地想象如何使用距离来突破并转换其余的用户体验。

其他设计提示,帮助您的应用实现空间化

  • 让事物不受限制:突破组件并为它们提供一些真实的(空间)空间。现在是时候为这些界面元素提供一些呼吸空间了。
  • 移除表面:隐藏背景,看看这对您的设计有何影响。
  • 通过动画激发灵感:您在应用中如何使用过渡?使用该角色想象您的应用突破到 VR 中。
  • 选择锚点:不要让用户在空间中迷失。让某个元素帮助收集或固定界面。

如需详细了解 XR 界面设计模式,请参阅 Android 开发者网站上的 Android XR 设计

空间界面基础知识

现在,我们已经介绍了 Ivy 在为 XR 设计 Androidify 时调整思维方式的经验,接下来我们来讨论开发空间界面。如果您习惯使用现代 Android 工具和库,那么使用 Jetpack XR SDK 开发空间界面应该会很熟悉。您会发现一些已经熟悉的概念,例如使用 Compose 创建布局。事实上,空间布局与使用行、列和分隔符的二维布局非常相似:

spatialrows.png

这些元素排列在 SpatialRows 和 SpatialColumns

此处显示的空间元素是 SpatialPanel 可组合项,可让您显示文本、按钮和视频等二维内容。

Subspace {
    SpatialPanel(
        SubspaceModifier
            .height(824.dp)
            .width(1400.dp)
    ) {
        Text("I'm a panel!")
    }
}

SpatialPanel 是子空间可组合项。子空间可组合项必须包含在子空间内,并由 SubspaceModifier 对象进行修改。子空间可以放置在应用界面层次结构中的任何位置,并且只能包含子空间可组合项。SubspaceModifier 对象与 Modifier 对象也非常相似:它们控制大小和位置等参数。

An Orbiter 可以附加到 SpatialPanel 并随其附加的内容一起移动。它们通常用于提供有关其附加内容的上下文控件,使内容成为主要焦点。它们可以放置在内容的任意四侧,距离可配置。

orbiter.png
Orbiter 附加到 SpatialPanel 的底部

还有许多其他空间界面元素,但这些是我们用于为 Androidify 创建空间布局的主要元素。

开始 XR 开发

我们先从项目设置开始。我们添加了 Jetpack XR Compose 依赖项,您可以在 Jetpack XR 依赖项 页面上找到该依赖项。

我们为将用户过渡到全沉浸空间的按钮添加了代码,首先检测是否具备此功能:

@Composable
fun couldRequestFullSpace(): Boolean =
   LocalSpatialConfiguration.current.hasXrSpatialFeature && 
   !LocalSpatialCapabilities.current.isSpatialUiEnabled
}

然后,我们为现有布局创建了一个新的按钮组件,该组件使用“展开内容”图标,并为其添加了 onClick 行为:

@Composable

fun RequestFullSpaceIconButton() {
   if (!couldRequestFullSpace()) return
   val session = LocalSession.current ?: return

   IconButton(
       onClick = {
           session.scene.requestFullSpaceMode()
       },
   ) {
       Icon(
           imageVector =  
               vectorResource(R.drawable.expand_content_24px),
           contentDescription = 
               stringResource("To Full Space"),
       )
   }
}

现在,点击该按钮只会显示全沉浸空间中的“中”布局。我们可以检查空间功能并确定是否可以显示空间界面 - 如果可以,我们将显示新的空间布局:

@Composable

fun HomeScreenContents(layoutType: HomeScreenLayoutType) {
   val layoutType = when {
      LocalSpatialCapabilities.current.isSpatialUiEnabled -> 
          HomeScreenLayoutType.Spatial
      isAtLeastMedium() -> HomeScreenLayoutType.Medium
      else -> HomeScreenLayoutType.Compact
   }

   when (layoutType) {
      HomeScreenLayoutType.Compact ->
          HomeScreenCompactPager(...)

      HomeScreenLayoutType.Medium ->
          HomeScreenMediumContents(...)

      HomeScreenLayoutType.Spatial ->
          HomeScreenContentsSpatial(...)
   }
}

实现主屏幕的设计

我们返回到全沉浸空间中主屏幕的空间设计,了解其实现方式。

customize_3.png

我们在此处确定了两个 SpatialPanel 元素:右侧包含视频卡片的面板,以及包含主界面的面板。最后,顶部附加了一个 Orbiter。我们先从视频播放器面板开始:

@Composable
fun HomeScreenContentsSpatial(...) {
   Subspace {
      SpatialPanel(SubspaceModifier
                   .fillMaxWidth(0.2f)
                   .fillMaxHeight(0.8f)
                   .aspectRatio(0.77f)
                   .rotate(0f, 0f, 5f),
      ) {
          VideoPlayer(videoLink)
      }
   }
}

我们只需将常规布局中的二维 VideoPlayer 组件重复使用到 SpatialPanel 中,无需进行任何其他更改!以下是其独立显示时的外观:

bluetiel.png

主内容面板也遵循相同的模式:我们在 SpatialPanel 中重复使用了中等面板内容。

SpatialPanel(SubspaceModifier.fillMaxSize(),
             resizePolicy = ResizePolicy(
                 shouldMaintainAspectRatio = true
             ),
             dragPolicy = MovePolicy()
) {
    Box {
        FillBackground(R.drawable.squiggle_full)
        HomeScreenSpatialMainContent(...)
    }
}

我们为此面板添加了 ResizePolicy,该政策会在面板边缘附近提供一些手柄,让用户可以调整面板的大小。它还具有 MovePolicy,可让用户拖动面板。

customize_4.png

将它们放置在同一子空间中会使它们彼此独立,因此我们将 VideoPlayer 面板设为主内容面板的子项。这样一来,当主内容面板通过父子关系被拖动时,VideoPlayer 面板也会移动。

@Composable
fun HomeScreenContentsSpatial(...) {
   Subspace {
       SpatialPanel(SubspaceModifier..., resizePolicy, dragPolicy) {
           Box {
               FillBackground(R.drawable.squiggle_full)
               HomeScreenSpatialMainContent(...)
           }
           Subspace {
              SpatialPanel(SubspaceModifier...) {
                  VideoPlayer(videoLink)
              }
           }
       }
   }
}

这就是我们制作第一个屏幕的方式!

接下来介绍其他屏幕

我还会简要介绍其他一些屏幕,重点介绍针对每个屏幕所做的具体考虑事项。

fullspace.png
全沉浸空间中的创建屏幕

在这里,我们使用了 SpatialRow 和 SpatialColumn 可组合项来创建适合推荐观看空间的布局,同样重复使用了中等布局中的组件。

fullspace_2.png

全沉浸空间中的结果屏幕:根据提示生成的机器人:红色棒球帽、飞行员太阳镜、浅蓝色 T 恤、红白格子短裤、绿色人字拖,并拿着网球拍。


结果屏幕使用羽化效果显示补充性引用,使其在屏幕边缘附近逐渐消失。它还在查看所用输入时使用实际的三维过渡,在空间中翻转图片。

发布到 Google Play 商店

现在,应用已准备好在 XR 中使用空间布局,接下来我们将其发布到 Play 商店。我们对应用的 AndroidManifest.xml 文件做了一个重要的最终更改:

<!-- Androidify can use XR features if they're available; they're not required. -->
<uses-feature android:name="android.software.xr.api.spatial" 
              android:required="false" />

这样一来,Play 商店就知道此应用具有 XR 差异化功能,并显示一个徽章,让用户知道该应用是专门为 XR 设计的:

androidify2.png
Android XR 上的 Google Play 商店中显示的 Androidify


上传发布版本时,我们无需执行任何特殊步骤即可为 XR 发布:与移动轨道上的用户一样,系统会将同一应用正常分发给 XR 设备上的用户!不过,您可以选择添加应用的 XR 专用屏幕截图,甚至可以使用空间视频素材资源上传应用的沉浸式预览。在 Android XR 设备上,Play 商店会自动将其显示为沉浸式三维预览,让用户在安装应用之前体验内容的深度和规模。

立即开始构建自己的体验

Androidify 是一个很好的示例,展示了如何将现有的二维 Jetpack Compose 应用空间化。今天,我们展示了为 Androidify 开发空间界面的完整过程,从设计到代码再到发布。我们修改了现有设计以适应空间范式,使用 SpatialPanel 和 Orbiter 可组合项创建在用户进入全沉浸空间时显示的空间布局,最后将新版应用发布到 Play 商店。

希望这篇博文能帮助您了解如何将自己的应用引入 Android XR!以下是一些可以帮助您入门的链接:

撰写者:
继续阅读