Mit Jetpack Compose für 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 bestehende Android-Benutzeroberfläche in den 3D-Raum erweitern oder komplett neue immersive 3D-Anwendungen erstellen.
Wenn Sie eine vorhandene Android Views-basierte App räumlich gestalten möchten, haben Sie mehrere Entwicklungsoptionen. Sie können Interoperabilitäts-APIs verwenden, Compose und Views zusammen nutzen oder direkt mit der SceneCore-Bibliothek arbeiten. Weitere Informationen finden Sie in unserem Leitfaden zum Arbeiten mit Ansichten.
Unterbereiche und räumlich dargestellte Komponenten
Wenn Sie Ihre App für Android XR entwickeln, ist es wichtig, die Konzepte von Unterraum und räumlich gerenderten Komponenten zu verstehen.
Unterbereich
Wenn Sie für Android XR entwickeln, müssen Sie Ihrer App oder Ihrem Layout ein Subspace
hinzufügen. Ein Unterbereich ist eine Partition des 3D-Raums in Ihrer App, in der Sie 3D-Inhalte platzieren, 3D-Layouts erstellen und 2D-Inhalten Tiefe verleihen können. Ein Unterraum wird nur gerendert, wenn die Räumlichkeit aktiviert ist. Im Home Space oder auf Nicht-XR-Geräten wird jeglicher Code in diesem Unterbereich ignoriert.
Es gibt zwei Möglichkeiten, einen Unterbereich zu erstellen:
Subspace
: Diese Composable-Funktion kann an beliebiger Stelle in der UI-Hierarchie Ihrer App platziert werden. So können Sie Layouts für 2D- und räumliche UIs beibehalten, ohne den Kontext zwischen Dateien zu verlieren. So können Sie beispielsweise die vorhandene App-Architektur einfacher zwischen XR und anderen Formfaktoren teilen, ohne den Status durch den gesamten UI-Baum übertragen oder Ihre App neu gestalten zu müssen.ApplicationSubspace
: Mit dieser Funktion wird nur ein Unterbereich auf App-Ebene erstellt. Sie muss auf der obersten Ebene in der räumlichen UI-Hierarchie Ihrer Anwendung platziert werden.ApplicationSubspace
rendert räumliche Inhalte mit optionalemVolumeConstraints
. Im Gegensatz zuSubspace
kannApplicationSubspace
nicht in einem anderenSubspace
oderApplicationSubspace
verschachtelt werden.
Weitere Informationen finden Sie unter Unterbereich zu Ihrer App hinzufügen.
Räumliche Komponenten
Subspace-Composables: Diese Komponenten können nur in einem Subspace gerendert werden.
Sie müssen in Subspace
oder setSubspaceContent()
eingeschlossen werden, bevor sie in einem 2D-Layout platziert werden. Mit einem SubspaceModifier
können Sie Ihren Subspace-Composables Attribute wie Tiefe, Versatz und Positionierung hinzufügen.
Andere räumlich gerenderte Komponenten müssen nicht in einem Unterraum aufgerufen werden. Sie bestehen aus herkömmlichen 2D-Elementen, die in einem räumlichen Container enthalten sind. Diese Elemente können in 2D- oder 3D-Layouts verwendet werden, wenn sie für beide definiert sind. Wenn die Räumlichkeit nicht aktiviert ist, werden die räumlichen Funktionen ignoriert und es wird auf die 2D-Entsprechungen zurückgegriffen.
Räumlichen Bereich erstellen
Ein SpatialPanel
ist ein untergeordneter Bereich, der zusammengesetzt werden kann und in dem App-Inhalte angezeigt werden können. Sie können beispielsweise die Videowiedergabe, Standbilder oder andere Inhalte in einem räumlichen Bereich anzeigen.
Mit SubspaceModifier
können Sie die Größe, das Verhalten und die Position des räumlichen Bereichs ändern, wie im folgenden Beispiel gezeigt.
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 ) } }
Wichtige Punkte zum Code
- Da
SpatialPanel
-APIs Subspace-kompatibel sind, müssen Sie sie inSubspace
aufrufen. Wenn Sie sie außerhalb eines Unterraums aufrufen, wird eine Ausnahme ausgelöst. - Die Größe des
SpatialPanel
wurde mit den Spezifikationenheight
undwidth
für dasSubspaceModifier
festgelegt. Wenn Sie diese Spezifikationen weglassen, wird die Größe des Bereichs durch die Abmessungen des Inhalts bestimmt. - Fügen Sie die Modifikatoren
movable
oderresizable
hinzu, damit der Nutzer die Größe des Bereichs ändern oder ihn verschieben kann. - Weitere Informationen zu Größe und Positionierung finden Sie in unserem Leitfaden zum Design von 3D-Panels. Weitere Informationen zur Code-Implementierung finden Sie in unserer Referenzdokumentation.
Funktionsweise eines verschiebbaren Unterraum-Modifiers
Wenn ein Nutzer ein Bedienfeld von sich wegbewegt, wird es standardmäßig durch einen verschiebbaren Unterraum-Modifikator ähnlich skaliert wie Bedienfelder, die vom System im Home-Bereich in der Größe angepasst werden. Dieses Verhalten wird auf alle Inhalte für Kinder angewendet. Wenn Sie diese Funktion deaktivieren möchten, setzen Sie den Parameter scaleWithDistance
auf false
.
Orbiter erstellen
Ein Orbiter ist eine räumliche UI-Komponente. Es ist für die Verknüpfung mit einem entsprechenden räumlichen Bereich, Layout oder einer anderen Einheit konzipiert. Ein Orbiter enthält in der Regel Navigations- und Kontextaktionen, die sich auf die Einheit beziehen, an die er angehängt 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 zu umschließen. Dadurch werden sie aus Ihrem 2D-Layout extrahiert und entsprechend Ihrer Konfiguration an das räumliche Panel angehängt.
Subspace { SpatialPanel( SubspaceModifier .height(824.dp) .width(1400.dp) .movable() .resizable() ) { 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 Orbiters räumliche UI-Komponenten sind, kann der Code in 2D- oder 3D-Layouts wiederverwendet werden. In einem 2D-Layout rendert Ihre App nur die Inhalte im Orbiter und ignoriert den Orbiter selbst.
- Weitere Informationen zur Verwendung und Gestaltung von Orbitern finden Sie in unseren Designrichtlinien.
Einem räumlichen Layout mehrere räumliche Bereiche hinzufügen
Sie können mehrere räumliche Bereiche erstellen und sie mit SpatialRow
, SpatialColumn
, SpatialBox
und SpatialLayoutSpacer
in einem räumlichen Layout platzieren.
Im folgenden Codebeispiel wird gezeigt, wie Sie dies umsetzen.
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
,SpatialBox
undSpatialLayoutSpacer
sind alle Subspace-Composables und müssen in einem Subspace platziert werden.- Verwenden Sie
SubspaceModifier
, um das Layout anzupassen. - Bei Layouts mit mehreren nebeneinander angeordneten Feldern empfehlen wir, mit einem
SubspaceModifier
einen Kurvenradius von 825 dp festzulegen, damit die Felder den Nutzer umgeben. Weitere Informationen finden Sie in unseren Designrichtlinien.
Mit einem Volume ein 3D-Objekt in Ihrem Layout platzieren
Wenn Sie ein 3D-Objekt in Ihrem Layout platzieren möchten, müssen Sie eine zusammensetzbare Unterraumfunktion namens „volume“ verwenden. Hier ist ein Beispiel dafür.
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, ) } } }
@OptIn(ExperimentalSubspaceVolumeApi::class) @Composable fun ObjectInAVolume(show3DObject: Boolean) {
Weitere Informationen
- Unter 3D-Modelle in Ihre App einfügen erfahren Sie, wie Sie 3D-Inhalte in ein Volume laden.
Oberfläche für Bild- oder Videoinhalte hinzufügen
Ein SpatialExternalSurface
ist eine zusammensetzbare Unterkomponente, die das Surface
erstellt und verwaltet, in das Ihre App Inhalte wie ein Bild oder ein Video zeichnen kann. SpatialExternalSurface
unterstützt stereoskopische oder monoskopische Inhalte.
In diesem Beispiel wird gezeigt, wie stereoskopische Side-by-Side-Videos mit Media3 ExoPlayer und SpatialExternalSurface
geladen werden:
@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
StereoMode
je nach Art der gerenderten Inhalte aufMono
,SideBySide
oderTopBottom
fest:Mono
: Das Bild oder der Videoframes besteht aus einem einzelnen, identischen Bild, das für beide Augen angezeigt wird.SideBySide
: Das Bild oder der Videoframes enthält ein Paar von Bildern oder Videoframes, die nebeneinander angeordnet sind. Das Bild oder der Frame auf der linken Seite stellt die Ansicht des linken Auges dar und das Bild oder der Frame auf der rechten Seite die Ansicht des rechten Auges.TopBottom
: Das Bild oder der Videoframes enthält ein vertikal gestapeltes Bild- oder Videoframes-Paar, wobei das Bild oder der Frame oben die Ansicht des linken Auges und das Bild oder der Frame unten die Ansicht des rechten Auges darstellt.
SpatialExternalSurface
unterstützt nur rechteckige Oberflächen.- Mit diesem
Surface
werden keine Eingabeereignisse erfasst. - Es ist nicht möglich,
StereoMode
-Änderungen mit dem Rendern von Anwendungen oder der Videodecodierung zu synchronisieren. - Diese Composable kann nicht vor anderen Bereichen gerendert werden. Sie sollten also keine verschiebbaren Modifikatoren verwenden, wenn sich andere Bereiche 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. Dadurch wird verhindert, dass die Inhalte per Bildschirmaufzeichnung aufgezeichnet oder von nicht sicheren Systemkomponenten aufgerufen werden.
Um eine sichere Oberfläche zu erstellen, legen Sie den Parameter surfaceProtection
für die zusammensetzbare Funktion SpatialExternalSurface
auf SurfaceProtection.Protected
fest.
Außerdem müssen Sie Media3 ExoPlayer mit den entsprechenden DRM-Informationen konfigurieren, damit der Lizenzerwerb von einem Lizenzserver abgewickelt werden kann.
Das folgende Beispiel zeigt, wie SpatialExternalSurface
und ExoPlayer
für die Wiedergabe eines DRM-geschützten Videostreams konfiguriert werden:
@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: Das Festlegen von
surfaceProtection = SurfaceProtection.Protected
aufSpatialExternalSurface
ist wichtig, damit der zugrunde liegendeSurface
durch sichere Puffer gesichert wird, die für DRM-Inhalte geeignet sind. - DRM-Konfiguration: Sie müssen
MediaItem
mit dem DRM-Schema (z. B.C.WIDEVINE_UUID
) und dem URI Ihres Lizenzservers konfigurieren. ExoPlayer verwendet diese Informationen, um die DRM-Sitzung zu verwalten. - Sicherer Inhalt: Wenn auf einer geschützten Oberfläche gerendert wird, werden die Videoinhalte auf einem sicheren Pfad decodiert und angezeigt. So können die Anforderungen der Inhaltslizenzierung erfüllt werden. Dadurch wird auch verhindert, dass die Inhalte in Screenshots angezeigt werden.
Andere räumliche UI-Komponenten hinzufügen
Räumliche UI-Komponenten können an beliebiger 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 räumliche Funktionen aktiviert sind. So können Sie Menüs, Dialogfeldern und anderen Komponenten eine Erhebung hinzufügen, ohne Ihren Code zweimal schreiben zu müssen. In den folgenden Beispielen für räumliche Benutzeroberflächen sehen Sie, wie Sie diese Elemente verwenden können.
UI-Komponente |
Wenn die Räumlichkeit aktiviert ist |
In 2D-Umgebung |
---|---|---|
|
Das Feld wird leicht nach hinten verschoben, um ein Dialogfeld anzuzeigen. |
Fällt zurück auf 2D |
|
Das Feld wird in der Z-Tiefe leicht nach hinten verschoben, um ein erhöhtes Pop-up anzuzeigen. |
Es wird auf ein 2D- |
|
|
Shows ohne räumliche Erhebung. |
SpatialDialog
Das 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 Feld angezeigt. Das Feld wird um 125 dp nach hinten verschoben, wenn die Räumlichkeit aktiviert ist. SpatialDialog
kann auch verwendet werden, wenn die Räumlichkeit nicht aktiviert ist. In diesem Fall wird SpatialDialog
auf das 2D-Pendant Dialog
zurückgesetzt.
@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 vonSpatialPopup
undSpatialElevation
ist sehr ähnlich. Weitere Informationen finden Sie in der API-Referenz.
Benutzerdefinierte Bereiche und Layouts erstellen
Wenn Sie benutzerdefinierte Panels erstellen möchten, die von Compose für XR nicht unterstützt werden, können Sie direkt mit PanelEntity
-Instanzen und dem Szenengraphen arbeiten. Verwenden Sie dazu die SceneCore
-APIs.
Orbitale an räumliche Layouts und andere Einheiten anpassen
Sie können einen Orbiter an jede in Compose deklarierte Entität anfügen. Dazu müssen Sie einen Orbiter in einem räumlichen Layout von UI-Elementen wie SpatialRow
, SpatialColumn
oder SpatialBox
deklarieren. Der Orbiter wird an der übergeordneten Einheit verankert, die 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 ein
SpatialPanel
eingebettet ist (wie in einem vorherigen Code-Snippet gezeigt), wird der Orbiter an diesemSpatialPanel
verankert. - In einem
Subspace
wird der Orbiter an der nächstgelegenen übergeordneten Einheit verankert, also dem räumlichen Layout, in dem der Orbiter deklariert ist.
Im folgenden Beispiel sehen Sie, 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 im
SpatialRow
verankert, in dem er deklariert ist. - Räumliche Layouts wie
SpatialRow
,SpatialColumn
undSpatialBox
haben alle inhaltslose Einheiten, die mit ihnen verknüpft sind. Daher wird ein Orbiter, der in einem räumlichen Layout deklariert wird, an dieses Layout angehängt.
Siehe auch
- 3D-Modelle in Ihre App einfügen
- Benutzeroberfläche für Android-Apps auf Grundlage von Ansichten entwickeln
- Material Design für XR implementieren