透過適用於 XR 的 Jetpack Compose,您可以使用熟悉的 Compose 概念 (例如行和欄),以宣告式方式建構空間 UI 和版面配置。這可讓您將現有的 Android UI 擴展至 3D 空間,或建立全新的沈浸式 3D 應用程式。
如果您要將現有的以 Android View 為基礎的應用程式轉換為空間化應用程式,有幾種開發選項可供選擇。您可以使用互通性 API、同時使用 Compose 和 View,或直接使用 SceneCore 程式庫。詳情請參閱如何使用 View 的指南。
關於子空間和空間化元件
為 Android XR 編寫應用程式時,請務必瞭解「子空間」和「空間化元件」的概念。
關於子空間
開發 Android XR 時,您必須在應用程式或版面配置中新增子區塊。子空間是應用程式內 3D 空間的分區,可在其中放置 3D 內容、建構 3D 版面配置,以及為其他 2D 內容加上深度。只有在啟用空間化功能時,才會算繪子空間。在首頁空間或非 XR 裝置上,系統會忽略該子空間中的任何程式碼。
建立子空間的方法有兩種:
setSubspaceContent()
:此函式會建立應用程式層級的子區域。您可以在主要活動中呼叫此方法,方法與使用setContent()
相同。應用程式層級子空間的高度、寬度和深度均無限制,因此可為空間內容提供無限的畫布。Subspace
:這個可組合項可放置在應用程式 UI 階層中的任何位置,讓您維護 2D 和空間 UI 的版面配置,而不會在檔案之間遺失內容。這樣一來,您就能更輕鬆地在 XR 和其他板型之間共用現有應用程式架構,而無需透過整個 UI 樹狀結構提升狀態,或重新建構應用程式。
詳情請參閱「在應用程式中新增子區」。
關於空間化元件
子區塊可組合項:這些元件只能在子區塊中算繪。必須先將其包含在 Subspace
或 setSubspaceContent
中,才能放入 2D 版面配置中。SubspaceModifier
可讓您在子區塊可組合項中新增深度、偏移和位置等屬性。
其他空間化元件不需要在子空間中呼叫。這些元素由空間容器內的傳統 2D 元素組成。如果為 2D 和 3D 版面配置定義這些元素,即可在 2D 或 3D 版面配置中使用這些元素。未啟用空間化功能時,系統會忽略其空間化特徵,並改為使用 2D 特徵。
建立空間面板
SpatialPanel
是子區可組合項,可讓您顯示應用程式內容,例如在空間面板中顯示影片播放、靜態圖片或任何其他內容。
您可以使用 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 ) } }
程式碼的重點
- 由於
SpatialPanel
API 是子區塊可組合項,因此您必須在Subspace
或setSubspaceContent
中呼叫這些 API。在子空間外呼叫這些方法會擲回例外狀況。 - 新增
movable
或resizable
修飾符,讓使用者調整面板大小或移動面板。 - 如要進一步瞭解尺寸和位置,請參閱空間面板設計指南。如要進一步瞭解程式碼實作方式,請參閱參考說明文件。
可移動子區域修飾符的運作方式
當使用者將面板移離時,可移動的子空間修飾符會以與系統在主畫面中調整面板大小的方式縮放面板。所有子項內容都會繼承這項行為。如要停用此功能,請將 scaleWithDistance
參數設為 false
。
建立軌道器
Orbiter 是空間 UI 元件。這類實體的設計目的是附加至對應的空間面板、版面配置或其他實體。軌道器通常會包含與所連結實體相關的導覽和內容相關動作項目。舉例來說,如果您已建立空間面板來顯示影片內容,就可以在軌道器中新增影片播放控制項。
如以下範例所示,請在 SpatialPanel
的 2D 版面配置中呼叫 orbiter,以包裝導覽等使用者控制項。這麼做會從 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 版面配置中,應用程式只會轉譯軌道器內的內容,並忽略軌道器本身。
- 如要進一步瞭解如何使用及設計 Orbiter,請參閱設計指南。
在空間版面配置中新增多個空間面板
您可以使用 SpatialRow
、SpatialColumn
、SpatialBox
和 SpatialLayoutSpacer
建立多個空間面板,並將這些面板放置在空間版面配置中。
以下程式碼範例說明如何執行這項操作。
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 ) } }
程式碼的重點
SpatialRow
、SpatialColumn
、SpatialBox
和SpatialLayoutSpacer
都是子集可組合函式,必須放置在子集中。- 使用
SubspaceModifier
自訂版面配置。 - 如果版面配置在一行中有多個面板,建議您使用
SubspaceModifier
設定曲線半徑 825dp,讓面板環繞使用者。詳情請參閱設計指南。
使用容積將 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) {
其他資訊
- 請參閱「在應用程式中新增 3D 模型」,進一步瞭解如何在單一卷中載入 3D 內容。
新增圖片或影片內容的介面
SpatialExternalSurface
是子區塊可組合項,可建立及管理 Surface
,讓應用程式繪製圖片或影片等內容。SpatialExternalSurface
支援立體或單眼內容。
本範例說明如何使用 Media3 Exoplayer 和 SpatialExternalSurface
載入並排立體影片:
@Composable fun SpatialExternalSurfaceContent() { val context = LocalContext.current Subspace { SpatialExternalSurface( modifier = SubspaceModifier .width(1200.dp) // Default width is 400.dp if no width modifier is specified .height(676.dp), // Default height is 400.dp if no height modifier is specified // Use StereoMode.Mono, StereoMode.SideBySide, or StereoMode.TopBottom, depending // upon which type of content you are rendering: monoscopic content, side-by-side stereo // content, or top-bottom stereo content stereoMode = StereoMode.SideBySide, ) { val exoPlayer = remember { ExoPlayer.Builder(context).build() } val videoUri = Uri.Builder() .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) // Represents a side-by-side stereo video, where each frame contains a pair of // video frames arranged side-by-side. The frame on the left represents the left // eye view, and the frame on the right represents the right eye view. .path("sbs_video.mp4") .build() val mediaItem = MediaItem.fromUri(videoUri) // onSurfaceCreated is invoked only one time, when the Surface is created onSurfaceCreated { surface -> exoPlayer.setVideoSurface(surface) exoPlayer.setMediaItem(mediaItem) exoPlayer.prepare() exoPlayer.play() } // onSurfaceDestroyed is invoked when the SpatialExternalSurface composable and its // associated Surface are destroyed onSurfaceDestroyed { exoPlayer.release() } } } }
程式碼的重點
- 視您要轉譯的內容類型而定,將
StereoMode
設為Mono
、SideBySide
或TopBottom
:Mono
:圖片或影片影格由單一相同圖片組成,並同時顯示在兩眼。SideBySide
:圖片或影片影格包含一對並排排列的圖片或影片影格,其中左側的圖片或影格代表左眼視角,右側的圖片或影格代表右眼視角。TopBottom
:圖片或影片框架包含一對垂直堆疊的圖片或影片框架,其中頂端的圖片或框架代表左眼視角,底部的圖片或框架代表右眼視角。
SpatialExternalSurface
僅支援矩形表面。- 這個
Surface
不會擷取輸入事件。 - 無法將
StereoMode
變更與應用程式算繪或影片解碼同步。 - 此可組合項無法在其他面板前方算繪,因此如果版面配置中已有其他面板,就不要使用可移動的修飾符。
新增其他空間 UI 元件
空間 UI 元件可放置在應用程式 UI 階層中的任何位置。這些元素可在 2D UI 中重複使用,且只有在啟用空間功能時,才會顯示空間屬性。這樣一來,您就能在選單、對話方塊和其他元件中加入升高效果,而無須編寫程式碼兩次。請參閱下列空間 UI 範例,進一步瞭解如何使用這些元素。
UI 元件 |
啟用空間化功能時 |
在 2D 環境中 |
---|---|---|
|
面板會在 z 深度中稍微往後推,以便顯示升高的對話方塊 |
會回退至 2D |
|
面板會在 z 深度中稍微往後推,以便顯示升高的彈出式視窗 |
會回復為 2D |
|
|
不含空間高度的節目。 |
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") } } } } }
程式碼的重點
- 以下是
SpatialDialog
的範例。使用SpatialPopup
和SpatialElevation
的方式非常相似。詳情請參閱 API 參考資料。
建立自訂面板和版面配置
如要建立 Compose for XR 不支援的自訂面板,您可以使用 SceneCore
API 直接操作 PanelEntities
和場景圖表。
將軌道錨定至空間版面配置和其他實體
您可以將 Orbiter 錨定至 Compose 中宣告的任何實體。這包括在 UI 元素的空間版面配置中宣告軌道器,例如 SpatialRow
、SpatialColumn
或 SpatialBox
。Orbiter 會將錨點設在最近的父項實體,也就是您宣告的位置。
Orbiter 的行為取決於您宣告的位置:
- 在
SpatialPanel
中包裝的 2D 版面配置中 (如前面程式碼片段所示),Orbiter 會錨定至該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 版面配置外宣告 Orbiter 時,Orbiter 會錨定至最近的上層實體。在這種情況下,Orbiter 會將錨點設為宣告
SpatialRow
的位置頂端。 SpatialRow
、SpatialColumn
、SpatialBox
等空間版面配置都與無內容實體相關聯。因此,在空間版面配置中宣告的 Orbiter 會錨定至該版面配置。