使用 Jetpack Compose 為 XR 開發 UI

透過 XR 適用的 Jetpack Compose,您可以使用熟悉的 Compose 概念 (例如行和欄),以宣告式方式建構空間 UI 和版面配置。這樣一來,您就能將現有的 Android UI 擴展至 3D 空間,或建立全新的沉浸式 3D 應用程式。

如果您要將現有的以 Android View 為基礎的應用程式轉換為空間化應用程式,可以選擇多種開發選項。您可以使用互通性 API、同時使用 Compose 和 View,或直接使用 SceneCore 程式庫。詳情請參閱如何使用 View 的指南

關於子空間和空間化元件

為 Android XR 編寫應用程式時,請務必瞭解「子空間」和「空間化元件」的概念。

關於子空間

開發 Android XR 時,您需要在應用程式或版面配置中新增子區塊。子空間是應用程式中 3D 空間的區隔,可用於放置 3D 內容、建構 3D 版面配置,以及為 2D 內容新增深度。只有在啟用空間化功能時,才會算繪子空間。在 Home Space 或非 XR 裝置上,系統會忽略該子空間中的任何程式碼。

建立子空間的方法有兩種:

  • setSubspaceContent:此函式會建立應用程式層級子區域。您可以在 MainActivity 中呼叫此方法,方法與使用 setContent 相同。應用程式層級子空間的高度、寬度和深度皆無限制,基本上可為空間內容提供無限的畫布。
  • Subspace:這個可組合項可放置在應用程式 UI 階層中的任何位置,讓您維護 2D 和空間 UI 的版面配置,而不會在檔案之間遺失內容。這樣一來,您就能更輕鬆地在 XR 和其他板型之間共用現有應用程式架構,而不需要透過整個 UI 樹狀結構提升狀態,或重新設計應用程式。

詳情請參閱「在應用程式中新增子區塊」。

關於空間化元件

子區塊可組合項:這些元件只能在子區塊中算繪。必須先將其包含在 SubspacesetSubspaceContent 中,才能放入 2D 版面配置中。SubspaceModifier 可讓您在子區域可組合項中新增深度、偏移和位置等屬性。

  • 子區域修飾符注意事項:請仔細留意 SubspaceModifier API 的順序。
    • 偏移量必須先出現在修飾符鏈結中
    • 可移動和可調整大小的屬性必須最後出現
    • 旋轉必須在縮放前套用

其他空間化元件不需要在子空間中呼叫。這些元素由空間容器內的傳統 2D 元素組成。如果為 2D 和 3D 版面配置定義這些元素,即可在 2D 或 3D 版面配置中使用這些元素。未啟用空間化功能時,系統會忽略其空間化特徵,並改為使用 2D 特徵。

建立空間面板

SpatialPanel 是子區域可組合項,可讓您顯示應用程式內容,例如在空間面板中顯示影片播放、靜態圖片或任何其他內容。

空間 UI 面板示例

您可以使用 SubspaceModifier 變更空間面板的大小、行為和位置,如以下範例所示。

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

// 2D content placed within the spatial panel
@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
        )
    }
}

程式碼的重點

  • 子區塊修飾符的注意事項:請仔細留意 SubspaceModifier API 的順序。
    • 偏移值必須先出現在修飾符鏈結中。
    • 可移動和可調整大小的修飾符必須放在最後。
    • 旋轉必須在縮放前套用。
  • 由於 SpatialPanel API 是子區可組合項,因此您必須在 SubspacesetSubspaceContent 中呼叫這些 API。在子空間之外呼叫這些方法會擲回例外狀況。
  • 新增 .movable.resizable SubspaceModifier,讓使用者調整面板大小或移動面板。
  • 如要進一步瞭解尺寸和位置,請參閱空間面板設計指南。如要進一步瞭解程式碼導入方式,請參閱參考說明文件

建立軌道器

Orbiter 是空間 UI 元件。這類面板的設計目的是附加至對應的空間面板,並包含與該空間面板相關的導覽和關聯動作項目。舉例來說,如果您已建立用於顯示影片內容的空間面板,就可以在軌道器中新增影片播放控制項。

軌道器示例

如以下範例所示,請在 SpatialPanel 中呼叫 Orbiter,以便包裝導覽等使用者控制項。這麼做會從 2D 版面配置中擷取這些元素,並根據您的設定將其附加至空間面板。

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

//2D content inside Orbiter
@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
                )
            }
        }
    }
}

程式碼的重點

  • 關於子空間修飾符的注意事項:請仔細留意 SubspaceModifier API 的順序。
    • 偏移量必須先出現在修飾符鏈結中
    • 可移動和可調整大小的屬性必須最後出現
    • 旋轉必須在縮放前套用
  • 由於軌道器是空間 UI 元件,因此程式碼可在 2D 或 3D 版面配置中重複使用。在 2D 版面配置中,應用程式只會轉譯軌道器內的內容,並忽略軌道器本身。
  • 如要進一步瞭解如何使用及設計 Orbiter,請參閱設計指南

在空間版面配置中新增多個空間面板

您可以使用 SpatialRowSpatialColumnSpatialBoxSpatialLayoutSpacer 建立多個空間面板,並將這些面板放置在 SpatialLayout 中。

空間版面配置中的多個空間面板範例

以下程式碼範例說明如何執行這項操作。

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 物件,您必須使用名為「體積」的子區可組合項。以下提供範例說明如何執行這項操作。

版面配置中的 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) {
    val xrCoreSession = checkNotNull(LocalSession.current)
    val scope = rememberCoroutineScope()
    if (show3DObject) {
        Subspace {
            Volume(
                modifier = SubspaceModifier
                    .offset(volumeXOffset, volumeYOffset, volumeZOffset) //
Relative position
                    .scale(1.2f) // Scale to 120% of the size

            ) { parent ->
                scope.launch {
                   // Load your 3D Object here
                }
            }
        }
    }
}

程式碼的重點

  • 關於子空間修飾符的注意事項:請仔細留意 SubspaceModifier API 的順序。
    • 偏移量必須先出現在修飾符鏈結中
    • 可移動和可調整大小的屬性必須最後出現
    • 旋轉必須在縮放前套用
  • 請參閱「新增 3D 內容」,進一步瞭解如何在卷中載入 3D 內容。

新增其他空間 UI 元件

空間 UI 元件可放置在應用程式 UI 階層中的任何位置。這些元素可在 2D UI 中重複使用,且只有在啟用空間功能時,才會顯示空間屬性。這樣一來,您就能在選單、對話方塊和其他元件中加入升高效果,而無須編寫兩次程式碼。請參閱下列空間 UI 範例,進一步瞭解如何使用這些元素。

UI 元件

啟用空間化功能時

在 2D 環境中

SpatialDialog

面板會在 z 深度中稍微往後推,以便顯示升高的對話方塊

會回退至 2D Dialog

SpatialPopUp

面板會在 z 深度中稍微往後推,以便顯示升高的彈出式視窗

會回復為 2D PopUp

SpatialElevation

SpatialElevationLevel 可設定為新增標高。

不含空間地形的節目。

SpatialDialog

以下是開啟對話方塊的範例,該對話方塊會在短暫延遲後開啟。使用 SpatialDialog 時,對話方塊會顯示在與空間化面板相同的 z 深度,且在啟用空間化功能時,面板會向後推 125dp。即使未啟用空間化功能,SpatialDialog 仍可使用,並會改為使用 2D 對應項目:Dialog

@Composable
fun DelayedDialog() {
   var showDialog by remember { mutableStateOf(false) }
   LaunchedEffect(Unit) {
       Handler(Looper.getMainLooper()).postDelayed({
           showDialog = true
       }, 3000)
   }
   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 和場景圖表。

另請參閱