使用 Jetpack Compose 為 XR 開發 UI

透過 Jetpack Compose for XR,您可以使用熟悉的 Compose 概念 (例如列和欄),以宣告方式建構空間 UI 和版面配置。這可讓您將現有 Android UI 擴充為 3D 空間,或建構全新的沉浸式 3D 應用程式。

如要調整現有 Android View 型應用程式的空間,您有幾種開發選項。您可以使用互通性 API、搭配使用 Compose 和 View,或是直接與 SceneCore 程式庫搭配使用。詳情請參閱使用檢視區塊的指南

關於子空間和空間元件

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

關於子空間

針對 Android XR 開發時,您必須在應用程式或版面配置中加入子空間。子空間是應用程式內的 3D 空間分區,您可以在其中放置 3D 內容、建構 3D 版面配置,並針對其他 2D 內容新增深度。只有在啟用空間化功能時,系統才會顯示子空間。在 Home Space 或非 XR 裝置上,系統會忽略該子空間中的所有程式碼。

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

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

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

關於空間元件

子空間可組合項:這些元件只能在子空間中顯示。而且它們必須在 SubspacesetSubspaceContent 內,才能放在 2D 版面配置中。SubspaceModifier 可讓您在子空間可組合項中新增深度、偏移和位置等屬性。

其他空間化元件不需要在子空間內呼叫。由空間容器包裝的傳統 2D 元素組成。如果兩者都有定義,這些元素將可用於 2D 或 3D 版面配置中。如未啟用空間化功能,系統會忽略其空間特徵,並改回使用 2D 對應項目。

建立空間面板

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

空間 UI 面板範例

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

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

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

程式碼的重點

建立軌道

軌道是空間 UI 元件,這種設計要附加至對應的空間面板、版面配置或其他實體。軌道通常包含與其錨定實體相關的導覽和關聯操作項目。舉例來說,假設您建立空間面板來顯示影片內容,就可以在軌道中新增影片播放控制項。

軌道範例

如以下範例所示,在 SpatialPanel 中呼叫 2D 版面配置內的軌道,納入導覽等使用者控制項。這樣做可從 2D 版面配置擷取這些變數,並根據您的設定附加到空間面板。

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

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

程式碼的重點

  • 由於軌道是空間 UI 元件,因此可以在 2D 或 3D 版面配置中重複使用程式碼。在 2D 版面配置中,應用程式只會轉譯軌道內部的內容,並忽略軌道本身。
  • 如要進一步瞭解如何使用和設計軌跡,請參閱設計指南

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

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

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

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

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) {

程式碼的重點

新增其他空間 UI 元件

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

UI 元件

啟用空間化功能後

2D 環境

SpatialDialog

面板會稍微往 Z 深度推進,以顯示更高的對話方塊

變回 2D Dialog

SpatialPopup

面板會稍微向 Z 深度推送,以顯示層級較高的彈出式視窗

回復為 2D Popup

SpatialElevation

您可以設定 SpatialElevationLevel 來增加高度。

沒有空間高度的節目。

SpatialDialog

這是在短暫延遲後開啟的對話方塊範例。使用 SpatialDialog 時,對話方塊會以與空間面板相同的 z 深度顯示,而當空間化功能啟用時,面板將向後退 125dp。在未啟用空間化的情況下,也可使用 SpatialDialog,在這種情況下,SpatialDialog 會改回使用 2D 對應項目 Dialog

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

將軌道固定在空間版面配置和其他實體中

您可以將軌道錨定在 Compose 中宣告的任何實體。這包括在 UI 元素 (例如 SpatialRowSpatialColumnSpatialBox) 的空間配置中宣告軌道。仲裁者錨點會位於您宣告該父系實體附近的父系實體。

仲裁者的行為取決於您的宣告位置:

  • SpatialPanel 納入的 2D 版面配置中 (如前面的程式碼程式碼片段所示),軌道錨點會指向該 SpatialPanel
  • Subspace 中,軌道錨點會指向最近的父實體,也就是軌道所宣告的空間版面配置。

以下範例說明如何將軌道固定在空間列:

Subspace {
    SpatialRow {
        Orbiter(
            position = OrbiterEdge.Top,
            offset = EdgeOffset.inner(8.dp),
            shape = SpatialRoundedCornerShape(size = CornerSize(50))
        ) {
            Text(
                "Hello World!",
                style = MaterialTheme.typography.h2,
                modifier = Modifier
                    .background(Color.White)
                    .padding(16.dp)
            )
        }
        SpatialPanel(
            SubspaceModifier
                .height(824.dp)
                .width(1400.dp)
        ) {
            Box(
                modifier = Modifier
                    .background(Color.Red)
            )
        }
        SpatialPanel(
            SubspaceModifier
                .height(824.dp)
                .width(1400.dp)
        ) {
            Box(
                modifier = Modifier
                    .background(Color.Blue)
            )
        }
    }
}

程式碼的重點

  • 當您宣告 2D 版面配置以外的軌道時,軌道錨點會位於其最鄰近的父系實體。在這個範例中,軌道錨點會位於所宣告 SpatialRow 的頂端。
  • 例如 SpatialRowSpatialColumnSpatialBox 等空間版面配置,都含有與無內容實體相關聯的無內容實體。因此,在該版面配置的空間版面配置錨點中宣告的軌道。

另請參閱