Räumliche Benutzeroberfläche mit Jetpack Compose für XR entwickeln

XR‑Geräte, für die der Leitfaden gilt
Dieser Leitfaden hilft Ihnen dabei, Erlebnisse für die folgenden Arten von XR-Geräten zu entwickeln.
XR‑Headsets
Kabelgebundene XR‑Brillen

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 übergeordneten Subspace, in dem sie verschachtelt ist. Subspace ist 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. PlanarEmbeddedSubspace berü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.

Beispiel für ein räumliches UI-Feld

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 in Subspace aufrufen. Wenn Sie sie außerhalb eines Unterbereichs aufrufen, wird eine Ausnahme ausgelöst.
  • Die Größe des SpatialPanel wurde mit den Angaben height und width für SubspaceModifier festgelegt. 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 resizable Unterbereichsmodifikator 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.

Beispiel für einen Orbiter

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.

Beispiel für mehrere räumliche Bereiche in einem räumlichen Layout

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, SpatialBox und SpatialSpacer sind 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 SubspaceModifier einen 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.status kö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 sizeAdapter gibt die Abmessungen der Entität an das Compose-Layoutsystem zurück.

Weitere Informationen

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 StereoMode je nach Art des zu rendernden Inhalts auf Mono, SideBySide oder TopBottom fest:
    • 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.
  • SpatialExternalSurface unterstützt nur rechteckige Oberflächen.
  • Diese Surface erfasst keine Eingabeereignisse.
  • Es ist nicht möglich, Änderungen an StereoMode mit 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.Protected für SpatialExternalSurface ist wichtig, damit die zugrunde liegende Surface durch sichere Puffer gesichert wird, die für DRM-Inhalte geeignet sind.
  • DRM-Konfiguration: Sie müssen das 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.
  • 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

SpatialDialog

Das Panel wird leicht nach hinten verschoben, um ein hervorgehobenes Dialogfeld anzuzeigen.

Fällt auf 2D Dialog zurück.

SpatialPopup

Das Panel wird leicht nach hinten verschoben, um ein hervorgehobenes Pop-up-Fenster anzuzeigen.

Fällt auf Popup in 2D zurück.

SpatialElevation

SpatialElevationLevel kann festgelegt werden, um eine Erhebung hinzuzufügen.

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

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 SpatialPanel eingeschlossen ist (wie in einem vorherigen Code beispiel gezeigt), wird der Orbiter an diesem SpatialPanel verankert.
  • In einem Subspace wird 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 SpatialRow verankert, in der er deklariert ist.
  • Räumliche Layouts wie SpatialRow, SpatialColumn und SpatialBox haben 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