瞭解 Android XR 基本知識:第 2 部分 - 軌道器與空間環境

1. 事前準備

課程內容

  • 可透過 Android XR 打造的獨特使用者體驗。
  • 如何使用 Jetpack Compose XR 程式庫,針對 Android XR 頭戴式裝置最佳化應用程式。
  • 如何使用 Jetpack Compose XR 程式庫中的 UI 元素。
  • 進一步瞭解如何建構 Android XR 應用程式的資源。

未提供的內容

軟硬體需求

建構項目

在本程式碼實驗室中,您將透過新增浮動式 UI 元素,以及自訂使用者在使用應用程式時周遭的虛擬環境,進一步運用現有的 XR 功能來最佳化應用程式。

起點

最終結果

2. 做好準備

取得程式碼

  1. 您可以在 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 中開啟專案後,請花點時間瀏覽範例程式碼。
  • 如果您尚未完成第一個程式碼實驗室,或尚未使用 Android XR 模擬器,請按照「在 Android XR 模擬器中執行應用程式」中的步驟執行應用程式。

3. 瞭解 XR 概念:軌道器

軌道器是完整空間模式中可用的浮動 UI 元素,通常用於控制空間面板或其他與其軌道錨定的實體中的內容。使用內容控制器的軌道可為內容提供更多空間,也就是說,使用者在主要內容保持可見的情況下,能夠快速存取軌道內的功能。軌道器可讓您靈活整合現有的 UI 元件 (例如導覽列),或建立新的元件。

此外,Orbiter API 可讓您在首頁空間模式或非 XR 裝置上,以平常方式算繪軌道器的內容,並在以完整空間模式執行時,自動將內容分割至單一軌道器。

在首頁空間模式下,這個導覽邊欄會在主要應用程式面板中算繪。

在完整空間模式下,導覽邊欄會分割至附加在主要面板的單一軌道器。

此時,應用程式頂端的應用程式列會包含一個按鈕,用於在首頁空間模式和完整空間模式之間切換。這個按鈕是完美的範例,說明在完整空間模式下執行時,控制項可包含在軌道器中。將控制項移至主面板的軌道,有助於凸顯控制項,同時也能以視覺方式表示,點選控制項時會將應用程式內容收合至該面板。

目前的狀態

實作內容

如要進一步瞭解設計軌道器的考量因素,請參閱「Spatial UI」。

4. 新增軌道器

納入空間模式切換鈕

如要將空間模式切換鈕轉換為軌道器,請在 Orbiter 可組合函式內納入 ToggleSpaceModeButton 可組合函式。

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

接著執行應用程式:在首頁空間模式下執行時,您會發現沒有任何變化。不過,當您點選切換鈕,應用程式進入完整空間模式時,您會發現按鈕不再位於頂端應用程式列,而是位於主要空間面板的右上方邊緣。

由於主要空間面板是 UI 樹狀結構中最近的父項空間實體,因此軌道器會錨定至該面板。軌道器相對於主要空間面板的確切位置,取決於 positionalignmentoffset 參數。請嘗試修改這些參數,查看這些參數支援的行為範圍。

首頁空間模式

完整空間模式

5. 瞭解 XR 概念:空間環境

使用 Orbiter 自訂 UI 元素在 3D 空間中的位置,是改善 XR 裝置使用者體驗的絕佳方法。您可以自訂使用者在使用應用程式時所處的空間環境,進一步提升體驗。

空間環境可納入深度、紋理和 3D 幾何圖形素材資源,打造豐富的視覺沉浸式體驗。這項功能是透過使用球形 天空盒圖片 (EXR 格式) 提供遠距全景背景和/或幾何圖形素材資源 (glTF 格式) 提供前景和中景元素,以便與天空盒混合。舉例來說,影片串流應用程式可以使用夜間天空盒,並搭配含有投影幕和車輛的汽車電影院 glTF。建立素材資源來設定使用者的空間環境時,請確保素材資源能達到高品質解析度,同時維持合理的檔案大小。詳情請參閱「最佳化環境素材資源」。

此外,您也可以控制空間環境的透明度。這可讓真實世界的影片串流穿過並與虛擬環境融合,協助使用者保持方向感

一位使用者站在岩石環境中,畫面中間有大型 UI 面板。

在下個步驟中,您將在應用程式中新增幾何圖形素材資源,並建立選單,讓使用者選擇環境。

如要進一步瞭解如何設計及實作空間環境,請參閱「空間環境」和「在應用程式中新增空間環境」。

6. 讓使用者變更空間環境

應用程式如何控制空間環境

開始前,建議您先瞭解應用程式控制空間環境的確切方式。

與面板中的內容不同,應用程式不會直接控制環境。而是可以與 SceneCore 工作階段互動,針對應用程式希望系統使用的環境提供偏好設定。這個偏好設定由 SpatialEnvironmentPreference 表示,其中包含天空盒 EXR 圖片和/或幾何圖形 glTF。應用程式提供偏好設定時的產生的效用,取決於應用程式設定偏好設定時的功能。如果應用程式具有變更環境的功能,系統會立即套用。如果沒有,系統會在應用程式獲得該功能時套用偏好設定。

舉例來說,應用程式在首頁空間模式下執行時,通常無法變更環境,但在完整空間模式下執行時,通常可以變更環境。因此,如果您允許使用者在首頁空間模式下設定環境偏好設定,則該偏好設定通常會等到應用程式以完整空間模式執行時才會生效。

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

在專案中新增環境素材資源

如要指定獨特的環境偏好設定,您需要使用天空盒和/或幾何圖形素材資源。在本程式碼研究室程式碼實驗室中,您只會使用 green_hills_ktx2_mipmap.glb 幾何圖形素材資源,您可以在包含解決方案程式碼的 part2 資料夾中找到這項素材資源,也可以前往 GitHub 下載。

  1. 在 Android Studio 的「Project」視窗中,對應用程式模組按一下滑鼠右鍵。接著,依序選取「New」>「Folder」>「Assets Folder」,然後按一下「Finish」建立資料夾。
  2. GLB 檔案新增至剛剛建立的 app/src/main/assets 資料夾。

模擬環境選項

如要簡化 UI 程式碼與系統 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)
        }
    }
}

請注意以下幾點:

  • 如果天空盒和幾何圖形皆為空值,系統會傳回空值,表示應使用預設的系統環境偏好設定。詳情請參閱 setSpatialEnvironmentPreference
  • skyboxgeometry 資源會以非同步方式建立,因為這些素材資源通常相當龐大,需要花費時間才能讀入記憶體。如果正式版應用程式經常切換環境,您可能需要考慮在記憶體中快取這些素材資源。

實作環境選取 UI

如要實作 UI,必須新增第二個軌道器,點選時會輪流顯示環境選項。

新增軌道器

  1. 在「Project」視窗中的 app 模組上按一下滑鼠右鍵,然後依序選取「New」 >「Vector Asset」。按一下「Clip art」欄位,搜尋並選取 landscape 素材資源 (在「填滿」圖示系列中),然後依序點選「OK」和「Next」建立素材資源。
  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 之外,這樣在切換首頁空間模式和完整空間模式時,狀態就會維持不變)。此外,請為目前的 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
                )
            )
        }
    }
)

再次執行應用程式,現在應該可以切換綠色山丘和預設環境!

b0e9571ef5f5597b.gif

7. 恭喜

如要繼續瞭解如何充分發揮 XR 的效益,請參閱下列資源和練習:

其他資訊

挑戰

  • 找出或建立其他環境素材資源,並將其新增為選項。
  • 修改環境控制器和 UI,讓使用者可以使用 setPassthroughOpacityPreference API 設定穿透偏好設定。請注意,用以控管穿透機制的能力與變更環境素材資源的能力是不一樣的東西。

參考文件