Mit Jetpack Compose for XR können Sie Ihre räumliche Benutzeroberfläche und Ihr Layout deklarativ mit bekannten Compose-Konzepten wie Zeilen und Spalten erstellen. So können Sie Ihre vorhandene Android-Benutzeroberfläche in den 3D‑Raum erweitern oder komplett neue immersive 3D‑Anwendungen entwickeln.
Wenn Sie eine vorhandene Android-App mit Ansichten räumlich gestalten, haben Sie mehrere Entwicklungsoptionen. Sie können Interoperabilitäts-APIs verwenden, Compose und Ansichten zusammen nutzen oder direkt mit der SceneCore-Bibliothek arbeiten. Weitere Informationen finden Sie in unserem Leitfaden zur Verwendung von Ansichten.
Unterbereiche und räumlich gestaltete Komponenten
Wenn Sie Ihre App für Android XR schreiben, ist es wichtig, die Konzepte von Unterbereich und räumlich gestalteten Komponenten zu verstehen.
Unterbereich
Bei der Entwicklung für Android XR müssen Sie Ihrer App oder Ihrem Layout einen Unterbereich hinzufügen. Ein Unterbereich ist eine Partition des 3D‑Raums in Ihrer App, in der Sie 3D‑Inhalte platzieren, 3D‑Layouts erstellen und sonstigen 2D‑Inhalten Tiefe verleihen können. Ein Unterbereich wird nur gerendert, wenn die räumliche Gestaltung aktiviert ist. Im Home Space oder auf Nicht-XR‑Geräten wird jeglicher Code in diesem Unterbereich ignoriert.
Es gibt mehrere Möglichkeiten, einen Unterbereich zu erstellen:
Subspace: Mit dieser zusammensetzbaren Funktion wird eine neue, unabhängige räumliche UI-Hierarchie erstellt. Sie erbt nicht die räumliche Position, Ausrichtung oder Skalierung eines übergeordnetenSubspace, in dem sie verschachtelt ist.Subspaceist automatisch an das vom System empfohlene Inhaltsfeld gebunden.PlanarEmbeddedSubspace: Diese zusammensetzbare Funktion kann in der UI-Hierarchie Ihrer App platziert werden, sodass Sie Layouts für 2D- und räumliche Benutzeroberflächen beibehalten können.PlanarEmbeddedSubspaceberücksichtigt die Einschränkungen und die Positionierung des übergeordneten Elements. Die darin platzierten 3D‑Inhalte werden dann relativ zu diesem 2D‑definierten Bereich positioniert.
Weitere Informationen finden Sie unter Unterbereich zu Ihrer App hinzufügen.
Räumlich gestaltete Komponenten
Zusammensetzbare Funktionen für Unterbereiche: Diese Komponenten können nur in einem Unterbereich gerendert werden.
Sie müssen in Subspace eingeschlossen werden, bevor sie in einem 2D‑Layout platziert werden.
Mit einem SubspaceModifier können Sie Ihren zusammensetzbaren Funktionen für Unterbereiche Attribute wie Tiefe, Versatz und
Positionierung hinzufügen.
Andere räumlich gestaltete Komponenten müssen nicht in einem Unterbereich aufgerufen werden. Sie bestehen aus herkömmlichen 2D‑Elementen, die in einem räumlichen Container eingeschlossen sind. Diese Elemente können in 2D‑ oder 3D‑Layouts verwendet werden, wenn sie für beide definiert sind. Wenn die räumliche Gestaltung nicht aktiviert ist, werden die räumlich gestalteten Funktionen ignoriert und die 2D‑Entsprechungen verwendet.
Räumliches Panel erstellen
Ein SpatialPanel ist eine zusammensetzbare Funktion für Unterbereiche, mit der Sie App
Inhalte anzeigen können. Sie können beispielsweise die Videowiedergabe, Standbilder oder andere
Inhalte in einem räumlichen Panel anzeigen.

Mit SubspaceModifier können Sie die Größe, das Verhalten und die Positionierung des räumlichen Panels ändern, wie im folgenden Beispiel gezeigt.
Subspace { SpatialPanel( SubspaceModifier .height(824.dp) .width(1400.dp), dragPolicy = MovePolicy(), resizePolicy = ResizePolicy(), ) { 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 ) } }
Wichtige Punkte zum Code
- Da
SpatialPanel-APIs zusammensetzbare Funktionen für Unterbereiche sind, müssen Sie sie inSubspaceaufrufen. Wenn Sie sie außerhalb eines Unterbereichs aufrufen, wird eine Ausnahme ausgelöst. - Die Größe des
SpatialPanelwurde mit den AngabenheightundwidthfürSubspaceModifierfestgelegt. Wenn Sie diese Angaben weglassen, wird die Größe des Panels durch die Abmessungen des Inhalts bestimmt. - Fügen Sie einen
movable-Unterbereichsmodifikator hinzu, damit Nutzer ein Panel verschieben können. - Fügen Sie einen
resizableUnterbereichsmodifikator hinzu, damit Nutzer die Größe eines Panels ändern können. - Weitere Informationen zu Größe und Positionierung finden Sie in unseren Richtlinien zum Design räumlicher Panels. Weitere Informationen zur Code-Implementierung finden Sie in unserer Referenzdokumentation.
Funktionsweise des movable-Modifikators
Wenn ein Nutzer ein Panel von sich wegbewegt, skaliert der movable Modifikator
das Panel standardmäßig auf ähnliche Weise wie Panels im
Home Spacevom System skaliert werden. Dieses Verhalten wird von allen untergeordneten Inhalten übernommen. Wenn Sie dies deaktivieren möchten, legen Sie den Parameter shouldScaleWithDistance auf false fest.
Orbiter erstellen
Ein Orbiter ist eine räumliche UI-Komponente. Er ist so konzipiert, dass er an einem
entsprechenden räumlichen Panel oder einer räumlichen Layoutkomponente wie SpatialColumn,
SpatialRow, oder SpatialBox angebracht wird. Ein Orbiter enthält in der Regel Navigations- und kontextbezogene Aktionselemente, die mit der Entität verknüpft sind, an der er verankert ist. Wenn Sie beispielsweise ein räumliches Panel zum Anzeigen von Videoinhalten erstellt haben, können Sie in einem Orbiter Steuerelemente für die Videowiedergabe hinzufügen.

Wie im folgenden Beispiel gezeigt, rufen Sie einen Orbiter im 2D‑Layout in einem SpatialPanel auf, um Nutzersteuerelemente wie die Navigation einzuschließen. Dadurch werden sie aus Ihrem 2D‑Layout extrahiert und gemäß Ihrer Konfiguration an das räumliche Panel angehängt.
Subspace { SpatialPanel( SubspaceModifier .height(824.dp) .width(1400.dp), dragPolicy = MovePolicy(), resizePolicy = ResizePolicy(), ) { SpatialPanelContent() OrbiterExample() } }
@Composable fun OrbiterExample() { Orbiter( position = ContentEdge.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 ) } } } }
Wichtige Punkte zum Code
- Da Orbiter räumliche UI-Komponenten sind, kann der Code in 2D‑ oder 3D‑Layouts wiederverwendet werden. In einem 2D‑Layout rendert Ihre App nur den Inhalt im Orbiter und ignoriert den Orbiter selbst.
- Weitere Informationen zur Verwendung und Gestaltung von Orbitern finden Sie in unseren Designrichtlinien.
Mehrere räumliche Panels zu einem räumlichen Layout hinzufügen
Sie können mehrere räumliche Panels erstellen und sie mit SpatialRow, SpatialColumn, SpatialBox und
SpatialSpacer in einem räumlichen Layout platzieren.

Das folgende Codebeispiel zeigt, wie das geht.
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 ) } }
Wichtige Punkte zum Code
SpatialRow,SpatialColumn,SpatialBoxundSpatialSpacersind allesamt zusammensetzbare Funktionen für Unterbereiche und müssen in einem Unterbereich platziert werden.- Verwenden Sie
SubspaceModifier, um Ihr Layout anzupassen. - Für Layouts mit mehreren Panels in einer Reihe empfehlen wir, mit einem
SubspaceModifiereinen Kurvenradius von 825 dp festzulegen, damit die Panels den Nutzer umgeben. Weitere Informationen finden Sie in unseren Designrichtlinien.
Mit SpatialGltfModel ein 3D‑Objekt zu Ihrem Layout hinzufügen
Android XR unterstützt das glTF-Format für 3D‑Modelle, die in der Regel als
.glb-Dateien gespeichert werden. Wenn Sie diese Objekte zu Ihrem Layout hinzufügen möchten, sollten Sie die
SpatialGltfModel zusammensetzbare Funktion verwenden. Diese API vereinfacht das Laden von Assets und die Verwaltung ihres Status.
Wenn Sie ein Modell anzeigen möchten, definieren Sie zuerst seine Quelle und seinen Status mit
rememberSpatialGltfModelState. Sie können
Modelle aus dem Ordner assets Ihrer App, einem URI oder
raw data laden.
val modelState = rememberSpatialGltfModelState( source = SpatialGltfModelSource.fromPath( Paths.get("models/model_name.glb") ) )
Sobald der Status definiert ist, verwenden Sie die zusammensetzbare Funktion SpatialGltfModel, um sie in einem Unterbereich zu rendern.
SpatialGltfModel(state = modelState, modifier = SubspaceModifier)
Wichtige Punkte zum Code
- Asynchrones Laden: Das Modell wird asynchron geladen. Bei der ersten Komposition kann die intrinsische Größe null sein. Das Layout wird neu gemessen, sobald das Modell bereit ist.
- Status steuern: Mit
SpatialGltfModelState.statuskönnen Sie den Ladestatus abfragen oder Animationen steuern. - Größe und Skalierung: Standardmäßig entspricht die Layoutgröße dem Begrenzungsrahmen des Assets. Sie können dies mit einem
SubspaceModifier.sizeüberschreiben, um das Modell einheitlich zu skalieren, damit es in die angegebenen Grenzen passt.
Mit einer SceneCoreEntity Entitäten in Ihrem Layout platzieren
Die SceneCoreEntity zusammensetzbare Funktion verbindet die Jetpack
SceneCore und Compose for XR Bibliotheken, sodass Sie
mit SceneCore erstellte Entitäten in Compose-Layouts verwenden können. So können Sie Entitäten und benutzerdefinierte Komponenten auf niedrigerer Ebene erstellen, während Compose die Größe und Positionierung dieser Entitäten festlegt, übergeordnete Elemente zuweist, untergeordnete Elemente hinzufügt und Modifikatoren anwendet.
Subspace { SceneCoreEntity( modifier = SubspaceModifier.offset(x = 50.dp), factory = { SurfaceEntity.create( session = session, pose = Pose.Identity, stereoMode = SurfaceEntity.StereoMode.MONO ) }, update = { entity -> // compose state changes may be applied to the // SceneCore entity here. entity.stereoMode = SurfaceEntity.StereoMode.SIDE_BY_SIDE }, sizeAdapter = SceneCoreEntitySizeAdapter({ IntSize2d(it.width, it.height) }), ) { // Content here will be children of the SceneCoreEntity // in the scene graph. } }
Wichtige Punkte zum Code
- Factory-Block: Hier initialisieren Sie die zugrunde liegende
SceneCore-Entität. - Update-Block: Mit dem Update-Block können Sie die Attribute der Entität als Reaktion auf Änderungen im Compose-Status ändern.
- Größenanpassung: Der
sizeAdaptergibt die Abmessungen der Entität an das Compose-Layoutsystem zurück.
Weitere Informationen
- Unter 3D‑Modelle zu Ihrer App hinzufügen erfahren Sie, wie Sie 3D
Inhalte in eine
SceneCoreEntityladen.
Oberfläche für Bild- oder Videoinhalte hinzufügen
Ein SpatialExternalSurface ist eine zusammensetzbare Funktion für Unterbereiche, mit der die Surface erstellt und
verwaltet wird, in die Ihre App Inhalte wie ein
Bild oder Video zeichnen kann. SpatialExternalSurface unterstützt stereoskopische oder monoskopische Inhalte.
In diesem Beispiel wird gezeigt, wie Sie mit
Media3 Exoplayer und SpatialExternalSurface stereoskopische Videos nebeneinander laden:
@OptIn(ExperimentalComposeApi::class) @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() } } } }
Wichtige Punkte zum Code
- Legen Sie
StereoModeje nach Art des zu rendernden Inhalts aufMono,SideBySideoderTopBottomfest:Mono: Das Bild oder der Videoframe besteht aus einem einzelnen, identischen Bild, das beiden Augen angezeigt wird.SideBySide: Das Bild oder der Videoframe enthält ein Paar nebeneinander angeordnete Bilder oder Video-Frames. Das Bild oder der Frame auf der linken Seite stellt die Ansicht für das linke Auge dar und das Bild oder der Frame auf der rechten Seite die Ansicht für das rechte Auge.TopBottom: Das Bild oder der Videoframe enthält ein Paar vertikal gestapelte Bilder oder Video-Frames. Das Bild oder der Frame oben stellt die Ansicht für das linke Auge dar und das Bild oder der Frame unten die Ansicht für das rechte Auge.
SpatialExternalSurfaceunterstützt nur rechteckige Oberflächen.- Diese
Surfaceerfasst keine Eingabeereignisse. - Es ist nicht möglich, Änderungen an
StereoModemit dem Rendering der Anwendung oder der Videodecodierung zu synchronisieren. - Diese zusammensetzbare Funktion kann nicht vor anderen Panels gerendert werden. Verwenden Sie daher keine
MovePolicy, wenn sich andere Panels im Layout befinden.
Oberfläche für DRM-geschützte Videoinhalte hinzufügen
SpatialExternalSurface unterstützt auch die Wiedergabe von DRM-geschützten Videostreams. Dazu müssen Sie eine sichere Oberfläche erstellen, die in geschützte Grafikpuffer gerendert wird. So wird verhindert, dass der Inhalt aufgezeichnet oder von nicht sicheren Systemkomponenten aufgerufen wird.
Wenn Sie eine sichere Oberfläche erstellen möchten, legen Sie den surfaceProtection Parameter auf
SurfaceProtection.Protected für die SpatialExternalSurface zusammensetzbare Funktion fest.
Außerdem müssen Sie Media3 Exoplayer mit den entsprechenden DRM
Informationen konfigurieren, um den Lizenzerwerb von einem Lizenzserver zu verarbeiten.
Das folgende Beispiel zeigt, wie Sie SpatialExternalSurface und ExoPlayer so konfigurieren, dass ein DRM-geschützter Videostream wiedergegeben wird:
@OptIn(ExperimentalComposeApi::class) @Composable fun DrmSpatialVideoPlayer() { val context = LocalContext.current Subspace { SpatialExternalSurface( modifier = SubspaceModifier .width(1200.dp) .height(676.dp), stereoMode = StereoMode.SideBySide, surfaceProtection = SurfaceProtection.Protected ) { val exoPlayer = remember { ExoPlayer.Builder(context).build() } // Define the URI for your DRM-protected content and license server. val videoUri = "https://your-content-provider.com/video.mpd" val drmLicenseUrl = "https://your-license-server.com/license" // Build a MediaItem with the necessary DRM configuration. val mediaItem = MediaItem.Builder() .setUri(videoUri) .setDrmConfiguration( MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID) .setLicenseUri(drmLicenseUrl) .build() ) .build() onSurfaceCreated { surface -> // The created surface is secure and can be used by the player. exoPlayer.setVideoSurface(surface) exoPlayer.setMediaItem(mediaItem) exoPlayer.prepare() exoPlayer.play() } onSurfaceDestroyed { exoPlayer.release() } } } }
Wichtige Punkte zum Code
- Geschützte Oberfläche: Die Einstellung
surfaceProtection = SurfaceProtection.ProtectedfürSpatialExternalSurfaceist wichtig, damit die zugrunde liegendeSurfacedurch sichere Puffer gesichert wird, die für DRM-Inhalte geeignet sind. - DRM-Konfiguration: Sie müssen das
MediaItemmit dem DRM-Schema (z. B.C.WIDEVINE_UUID) und dem URI Ihres Lizenzservers konfigurieren. ExoPlayer verwendet diese Informationen, um die DRM-Sitzung zu verwalten. - Sichere Inhalte: Beim Rendering auf einer geschützten Oberfläche werden die Videoinhalte decodiert und über einen sicheren Pfad angezeigt. So werden die Anforderungen der Inhaltslizenzierung erfüllt. Außerdem wird verhindert, dass die Inhalte in Screenshots angezeigt werden.
Andere räumliche UI-Komponenten hinzufügen
Räumliche UI-Komponenten können an einer beliebigen Stelle in der UI-Hierarchie Ihrer Anwendung platziert werden. Diese Elemente können in Ihrer 2D‑Benutzeroberfläche wiederverwendet werden. Ihre räumlichen Attribute sind nur sichtbar, wenn die räumlichen Funktionen aktiviert sind. So können Sie Menüs, Dialogfelder und andere Komponenten hervorheben, ohne den Code zweimal schreiben zu müssen. In den folgenden Beispielen für räumliche Benutzeroberflächen sehen Sie, wie Sie diese Elemente verwenden.
UI-Komponente |
Wenn die räumliche Gestaltung aktiviert ist |
In einer 2D‑Umgebung |
|---|---|---|
|
Das Panel wird leicht nach hinten verschoben, um ein hervorgehobenes Dialogfeld anzuzeigen. |
Fällt auf 2D |
|
Das Panel wird leicht nach hinten verschoben, um ein hervorgehobenes Pop-up-Fenster anzuzeigen. |
Fällt auf |
|
|
Wird ohne räumliche Erhebung angezeigt. |
SpatialDialog
Dies ist ein Beispiel für ein Dialogfeld, das nach einer kurzen Verzögerung geöffnet wird. Wenn
SpatialDialog verwendet wird, wird das Dialogfeld in derselben Z‑Tiefe wie das
räumliche Panel angezeigt. Das Panel wird um 125 dp nach hinten verschoben, wenn die räumliche Gestaltung
aktiviert ist. SpatialDialog kann auch verwendet werden, wenn die räumliche Gestaltung nicht aktiviert ist. In
diesem Fall fällt SpatialDialog auf die 2D‑Entsprechung Dialog zurück.
@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") } } } } }
Wichtige Punkte zum Code
- Dies ist ein Beispiel für
SpatialDialog. Die Verwendung vonSpatialPopupundSpatialElevationist sehr ähnlich. Weitere Informationen finden Sie in unserer API-Referenz für mehr Details.
Benutzerdefinierte Panels und Layouts erstellen
Wenn Sie benutzerdefinierte Panels erstellen möchten, die von Compose for XR nicht unterstützt werden, können Sie
direkt mit PanelEntity-Instanzen und dem Szenendiagramm arbeiten. Verwenden Sie dazu die
SceneCore-APIs.
Orbiter an räumlichen Panels und Layouts verankern
Sie können einen Orbiter an SpatialPanels und räumlichen Layoutkomponenten verankern, die in Compose deklariert sind. Dazu müssen Sie einen Orbiter in einem räumlichen Layout von UI-Elementen wie SpatialRow, SpatialColumn oder SpatialBox deklarieren. Der Orbiter wird an dem übergeordneten Element verankert, das sich am nächsten an der Stelle befindet, an der Sie ihn deklariert haben.
Das Verhalten des Orbiters hängt davon ab, wo Sie ihn deklarieren:
- In einem 2D‑Layout, das in einem
SpatialPaneleingeschlossen ist (wie in einem vorherigen Code beispiel gezeigt), wird der Orbiter an diesemSpatialPanelverankert. - In einem
Subspacewird der Orbiter an der nächstgelegenen übergeordneten Entität verankert, also an dem räumlichen Layout, in dem der Orbiter deklariert ist.
Das folgende Beispiel zeigt, wie Sie einen Orbiter an einer räumlichen Zeile verankern:
Subspace { SpatialRow { Orbiter( position = ContentEdge.Top, offset = 8.dp, offsetType = OrbiterOffsetType.InnerEdge, shape = SpatialRoundedCornerShape(size = CornerSize(50)) ) { Text( "Hello World!", style = MaterialTheme.typography.titleMedium, 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) ) } } }
Wichtige Punkte zum Code
- Wenn Sie einen Orbiter außerhalb eines 2D‑Layouts deklarieren, wird er an der nächstgelegenen übergeordneten Entität verankert. In diesem Fall wird der Orbiter oben an der
SpatialRowverankert, in der er deklariert ist. - Räumliche Layouts wie
SpatialRow,SpatialColumnundSpatialBoxhaben alle inhaltslose Entitäten, die mit ihnen verknüpft sind. Daher wird ein Orbiter, der in einem räumlichen Layout deklariert ist, an diesem Layout verankert.
Weitere Informationen
- 3D‑Modelle zu Ihrer App hinzufügen
- Benutzeroberfläche für Android-Apps mit Ansichten entwickeln
- Material Design für XR implementieren