XR 向け Jetpack Compose で UI を開発する

Jetpack Compose for XR を使用すると、行や列などの使い慣れた Compose コンセプトを使用して、空間 UI とレイアウトを宣言的に構築できます。これにより、既存の Android UI を 3D 空間に拡張したり、まったく新しい没入型 3D アプリを構築したりできます。

既存の Android ビューベースのアプリを空間化する場合は、いくつかの開発オプションがあります。相互運用 API を使用するか、Compose とビューを併用するか、SceneCore ライブラリを直接使用できます。詳しくは、ビューの操作に関するガイドをご覧ください。

サブスペースと空間化されたコンポーネントについて

Android XR 用アプリを作成する場合、サブスペースと空間化されたコンポーネントの概念を理解することが重要です。

サブスペースについて

Android XR 向けに開発する場合は、アプリまたはレイアウトにサブスペースを追加する必要があります。サブスペースは、アプリ内の 3D 空間のパーティションです。3D コンテンツを配置したり、3D レイアウトを作成したり、2D コンテンツに奥行きを追加したりできます。サブスペースは、空間化が有効になっている場合にのみレンダリングされます。ホーム スペースまたは XR 以外のデバイスでは、そのサブスペース内のコードは無視されます。

サブスペースを作成するには、次の 2 つの方法があります。

  • setSubspaceContent(): この関数は、アプリレベルのサブスペースを作成します。これは、setContent() を使用する場合と同じ方法でメイン アクティビティで呼び出すことができます。アプリレベルのサブスペースは高さ、幅、奥行きに制限がなく、基本的に空間コンテンツ用の無限のキャンバスを提供します。
  • Subspace: このコンポーザブルはアプリの UI 階層内の任意の場所に配置できるため、ファイル間のコンテキストを失うことなく、2D と空間 UI のレイアウトを維持できます。これにより、UI ツリー全体で状態をホイスティングしたり、アプリを再設計したりすることなく、既存のアプリ アーキテクチャを XR と他のフォーム ファクタ間で簡単に共有できます。

詳細については、アプリにサブスペースを追加するをご覧ください。

空間化されたコンポーネントについて

サブスペース コンポーザブル: これらのコンポーネントはサブスペースでのみレンダリングできます。2D レイアウト内に配置する前に、Subspace または setSubspaceContent で囲む必要があります。SubspaceModifier を使用すると、サブスペース コンポーザブルに深度、オフセット、配置などの属性を追加できます。

他の空間化されたコンポーネントは、サブスペース内で呼び出す必要はありません。空間コンテナ内にラップされた従来の 2D 要素で構成されています。これらの要素は、両方に定義されている場合は、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         )     } }  

コードに関する主なポイント

オビッターを作成する

オービターは空間 UI コンポーネントです。対応する空間パネル、レイアウト、その他のエンティティに接続するように設計されています。通常、オービターには、アンカーされているエンティティに関連するナビゲーション アイテムとコンテキスト アクション アイテムが含まれています。たとえば、動画コンテンツを表示する空間パネルを作成した場合は、オービター内に動画再生コントロールを追加できます。

オルビター(軌道上を周回する宇宙船)の例

次の例に示すように、SpatialPanel の 2D レイアウト内でオービターを呼び出して、ナビゲーションなどのユーザー コントロールをラップします。これにより、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                 )             }         }     } } 

コードに関する主なポイント

  • オービターは空間 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         )     } } 

コードに関する主なポイント

  • SpatialRowSpatialColumnSpatialBoxSpatialLayoutSpacer はすべてサブスペース コンポーザブルであり、サブスペース内に配置する必要があります。
  • SubspaceModifier を使用してレイアウトをカスタマイズします。
  • 1 行に複数のパネルがあるレイアウトの場合は、SubspaceModifier を使用してカーブの半径を 825dp に設定し、パネルがユーザーを囲むようにすることをおすすめします。詳しくは、設計に関するガイダンスをご覧ください。

ボリュームを使用して 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                 }             }         }     } }  

コードに関する主なポイント

その他の空間 UI コンポーネントを追加する

空間 UI コンポーネントは、アプリの UI 階層内の任意の場所に配置できます。これらの要素は 2D UI で再利用できます。空間属性は、空間機能が有効になっている場合にのみ表示されます。これにより、コードを 2 回記述しなくても、メニュー、ダイアログ、その他のコンポーネントにエレベーションを追加できます。これらの要素の使用方法を詳しく理解するには、空間 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) {        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 とシーングラフを直接操作します。

オビッターが空間レイアウトやその他のエンティティに固定される

オビッターは、Compose で宣言された任意のエンティティに固定できます。これには、SpatialRowSpatialColumnSpatialBox などの UI 要素の空間レイアウトでオービターを宣言することが含まれます。オービターは、宣言した場所に最も近い親エンティティにアンカーされます。

オビッターは、宣言する場所によって動作が決まります。

  • 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.titleLarge,                 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 などの空間レイアウトには、コンテンツのないエンティティが関連付けられています。したがって、空間レイアウトで宣言されたオービターは、そのレイアウトにアンカーされます。

関連ドキュメント