XR için Jetpack Compose ile üç boyutlu kullanıcı arayüzü geliştirme

Uygun XR cihazlar
Bu kılavuz, bu tür XR cihazlar için deneyimler oluşturmanıza yardımcı olur.
XR Kulaklığı
Kablolu XR Gözlükleri

XR için Jetpack Compose ile, satırlar ve sütunlar gibi tanıdık Compose kavramlarını kullanarak uzamsal kullanıcı arayüzünüzü ve düzeninizi bildirime dayalı olarak oluşturabilirsiniz. Bu sayede mevcut Android kullanıcı arayüzünüzü 3D alanına genişletebilir veya tamamen yeni, etkileyici 3D uygulamalar oluşturabilirsiniz.

Mevcut bir Android Views tabanlı uygulamayı uzamsallaştırıyorsanız çeşitli geliştirme seçenekleriniz vardır. Birlikte çalışabilirlik API'lerini kullanabilir, Compose ve Views'u birlikte kullanabilir veya doğrudan SceneCore kitaplığıyla çalışabilirsiniz. Daha fazla bilgi için Görünümlerle çalışma kılavuzumuza bakın.

Alt alanlar ve uzamsallaştırılmış bileşenler hakkında

Android XR için uygulamanızı yazarken alt uzay ve uzamsallaştırılmış bileşenler kavramlarını anlamanız önemlidir.

Alt alan hakkında

Android XR için geliştirme yaparken uygulamanıza veya düzeninize bir alt alan eklemeniz gerekir. Alt alan, uygulamanızdaki 3D alanın bir bölümüdür. Bu bölümde 3D içerik yerleştirebilir, 3D düzenler oluşturabilir ve aksi takdirde 2D olacak içeriklere derinlik katabilirsiniz. Bir alt alan yalnızca uzamsallaştırma etkinleştirildiğinde oluşturulur. Ana Sayfa Alanı'nda veya XR olmayan cihazlarda, bu alt alan içindeki tüm kodlar yoksayılır.

Alt alan oluşturmanın birkaç yolu vardır:

  • Subspace: Bu composable, yeni ve bağımsız bir Spatial UI hiyerarşisi oluşturur. İçine yerleştirildiği üst öğelerin (Subspace) uzamsal konumunu, yönünü veya ölçeğini devralmaz. Subspace, sistemin önerdiği içerik kutusuyla otomatik olarak sınırlandırılır.
  • PlanarEmbeddedSubspace: Bu composable, uygulamanızın kullanıcı arayüzü hiyerarşisine yerleştirilebilir. Böylece 2D ve uzamsal kullanıcı arayüzleri için düzenleri koruyabilirsiniz. PlanarEmbeddedSubspace, üst öğesinin kısıtlamalarına ve konumlandırmasına uyar. İçine yerleştirilen 3D içerik, bu 2D tanımlı alana göre konumlandırılır.

Daha fazla bilgi için Uygulamanıza alt alan ekleme başlıklı makaleyi inceleyin.

Uzamsallaştırılmış bileşenler hakkında

Subspace composable'ları: Bu bileşenler yalnızca bir subspace'te oluşturulabilir. 2D düzene yerleştirilmeden önce Subspace içine alınmalıdır. SubspaceModifier, alt alan composable'larınıza derinlik, dengeleme ve konumlandırma gibi özellikler eklemenize olanak tanır.

Diğer uzamsallaştırılmış bileşenlerin bir alt uzay içinde çağrılması gerekmez. Bunlar, uzamsal bir kapsayıcıya yerleştirilmiş geleneksel 2D öğelerden oluşur. Bu öğeler, her ikisi için de tanımlanmışsa 2D veya 3D düzenlerde kullanılabilir. Uzamsallaştırma etkinleştirilmediğinde, uzamsallaştırılmış özellikleri yoksayılır ve 2D karşılıklarına geri dönerler.

Uzamsal panel oluşturma

SpatialPanel, uygulama içeriğini görüntülemenize olanak tanıyan bir alt alan oluşturulabiliridir. Örneğin, video oynatma, sabit resimler veya başka herhangi bir içeriği uzamsal bir panelde görüntüleyebilirsiniz.

Uzamsal kullanıcı arayüzü paneli örneği

Aşağıdaki örnekte gösterildiği gibi, SubspaceModifier kullanarak uzamsal panelin boyutunu, davranışını ve konumunu değiştirebilirsiniz.

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
        )
    }
}

Kodla ilgili önemli noktalar

  • SpatialPanel API'leri alt alan composable'ları olduğundan bunları Subspace içinde çağırmanız gerekir. Bunları bir alt alanın dışında çağırmak istisna oluşturur.
  • SpatialPanel boyutları, SubspaceModifier üzerindeki height ve width özellikleri kullanılarak ayarlanmıştır. Bu spesifikasyonların atlanması, panelin boyutunun içeriğinin ölçüleriyle belirlenmesine olanak tanır.
  • movable alt alan değiştiricisi ekleyerek kullanıcının paneli taşımasına izin verin.
  • Kullanıcının resizable alt alan değiştiricisi ekleyerek bir paneli yeniden boyutlandırmasına izin verin.
  • Boyutlandırma ve konumlandırma ile ilgili ayrıntılar için uzamsal panel tasarım kılavuzumuzu inceleyin. Kod uygulamasıyla ilgili daha fazla bilgi için referans dokümanlarımızı inceleyin.

movable değiştiricisinin işleyiş şekli

Bir kullanıcı paneli kendisinden uzaklaştırdığında, varsayılan olarak movable değiştiricisi, paneli ev alanında sistem tarafından yeniden boyutlandırılan panellere benzer şekilde ölçeklendirir. Tüm çocuk içerikleri bu davranışı devralır. Bu özelliği devre dışı bırakmak için shouldScaleWithDistance parametresini false olarak ayarlayın.

Yörünge aracı oluşturma

Orbiter, uzamsal bir kullanıcı arayüzü bileşenidir. SpatialColumn, SpatialRow veya SpatialBox gibi ilgili bir uzamsal panele ya da uzamsal düzen bileşenine eklenmek üzere tasarlanmıştır. Bir yörünge öğesi genellikle bağlı olduğu öğeyle ilgili gezinme ve bağlamsal işlem öğelerini içerir. Örneğin, video içeriklerini göstermek için bir uzamsal panel oluşturduysanız yörüngeye video oynatma kontrolleri ekleyebilirsiniz.

Yörünge aracı örneği

Aşağıdaki örnekte gösterildiği gibi, gezinme gibi kullanıcı kontrollerini sarmalamak için 2D düzendeki bir yörünge aracını SpatialPanel içinde çağırın. Bu işlem, bunları 2D düzeninizden çıkarır ve yapılandırmanıza göre uzamsal panele ekler.

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
                )
            }
        }
    }
}

Kodla ilgili önemli noktalar

  • Yörüngeler, uzamsal kullanıcı arayüzü bileşenleri olduğundan kod, 2D veya 3D düzenlerde yeniden kullanılabilir. 2D düzende uygulamanız yalnızca orbiter'ın içindeki içeriği oluşturur ve orbiter'ı yoksayar.
  • Yörüngelerin nasıl kullanılacağı ve tasarlanacağı hakkında daha fazla bilgi için tasarım rehberimize göz atın.

Uzamsal düzene birden fazla uzamsal panel ekleme

SpatialRow, SpatialColumn, SpatialBox ve SpatialSpacer kullanarak birden fazla mekansal panel oluşturabilir ve bunları mekansal bir düzende yerleştirebilirsiniz.

Uzamsal düzendeki birden fazla uzamsal panel örneği

Aşağıdaki kod örneğinde bu işlemin nasıl yapılacağı gösterilmektedir.

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
        )
    }
}

Kodla ilgili önemli noktalar

  • SpatialRow, SpatialColumn, SpatialBox ve SpatialSpacer, alt alan composable'larıdır ve bir alt alan içinde yerleştirilmelidir.
  • Düzeninizi özelleştirmek için SubspaceModifier simgesini kullanın.
  • Arka arkaya birden fazla panel içeren düzenlerde, panellerin kullanıcınızı sarmalaması için SubspaceModifier kullanarak 825 dp eğrilik yarıçapı ayarlamanızı öneririz. Ayrıntılar için tasarım yönergelerimize bakın.

SpatialGltfModel'i kullanarak düzeninize 3D nesne ekleme

Android XR, 3D modeller için glTF biçimini destekler. Bu biçimdeki dosyalar genellikle .glb olarak kaydedilir. Bu nesneleri düzeninize eklemek için SpatialGltfModel composable'ını kullanmanız gerekir. Bu API, öğe yükleme ve durumlarını yönetme sürecini basitleştirir.

Bir modeli görüntülemek için önce rememberSpatialGltfModelState kullanarak kaynağını ve durumunu tanımlayın. Modelleri uygulamanızın assets klasöründen, URI veya raw data'dan yükleyebilirsiniz.

val modelState = rememberSpatialGltfModelState(
    source = SpatialGltfModelSource.fromPath(
        Paths.get("models/model_name.glb")
    )
)

Durum tanımlandıktan sonra, bunu bir alt alan içinde oluşturmak için SpatialGltfModel composable'ı kullanın.

SpatialGltfModel(state = modelState, modifier = SubspaceModifier)

Kodla ilgili önemli noktalar

  • Eşzamansız yükleme: Model eşzamansız olarak yüklenir. İlk oluşturma sırasında, öğenin doğal boyutu sıfır olabilir. Düzen, model hazır olduğunda yeniden ölçülür.
  • Kontrol durumu: Yükleme durumunu sorgulamak veya animasyonları kontrol etmek için SpatialGltfModelState.status simgesini kullanın.
  • Boyutlandırma ve ölçeklendirme: Düzen boyutu, varsayılan olarak öğenin sınırlayıcı kutusuyla eşleşir. Modeli belirtilen sınırlar içinde kalacak şekilde eşit olarak ölçeklendirmek için bunu SubspaceModifier.size ile geçersiz kılabilirsiniz.

Düzeninize öğe yerleştirmek için SceneCoreEntity kullanma

SceneCoreEntity composable'ı, Jetpack SceneCore ve XR için Compose kitaplıklarını birbirine bağlar. Böylece, SceneCore ile oluşturulan öğeleri Compose düzenlerinde kullanabilirsiniz. Bu sayede, Compose'un bu öğeleri boyutlandırmasına, konumlandırmasına, yeniden üst öğe atamasına, alt öğe eklemesine ve değiştiriciler uygulamasına izin verirken daha düşük düzeyli öğeler ve özel bileşenler oluşturabilirsiniz.

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.
    }
}

Kodla ilgili önemli noktalar

  • Fabrika bloğu: Fabrika bloğu, temel SceneCore öğesini başlattığınız yerdir.
  • Güncelleme bloğu: Compose durumunuzdaki değişikliklere yanıt olarak öğenin özelliklerini değiştirmek için güncelleme bloğunu kullanın.
  • Boyut Uyarlama: sizeAdapter, öğenin boyutlarını Compose düzen sistemine geri iletir.

Ek bilgiler

Resim veya video içeriği için yüzey ekleme

SpatialExternalSurface, uygulamanızın resim veya video gibi içerik çizebileceği Surface öğesini oluşturan ve yöneten, birleştirilebilir bir alt alan. SpatialExternalSurface, stereoskopik veya monoskopik içeriği destekler.

Bu örnekte, Media3 Exoplayer ve SpatialExternalSurface kullanılarak yan yana stereoskopik video yükleme işlemi gösterilmektedir:

@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() }
        }
    }
}

Kodla ilgili önemli noktalar

  • Hangi tür içeriği oluşturduğunuza bağlı olarak StereoMode değerini Mono, SideBySide veya TopBottom olarak ayarlayın:
    • Mono: Resim veya video karesi, her iki göze de gösterilen tek ve aynı resimden oluşur.
    • SideBySide: Resim veya video karesi, yan yana düzenlenmiş bir çift resim ya da video karesi içerir. Soldaki resim veya kare, sol göz görünümünü; sağdaki resim veya kare ise sağ göz görünümünü temsil eder.
    • TopBottom: Resim veya video karesi, dikey olarak üst üste yerleştirilmiş bir çift resim ya da video karesi içerir. Üstteki resim veya kare sol göz görünümünü, alttaki resim veya kare ise sağ göz görünümünü temsil eder.
  • SpatialExternalSurface yalnızca dikdörtgen yüzeyleri destekler.
  • Bu Surface, giriş etkinliklerini yakalamaz.
  • StereoMode değişikliklerini uygulama oluşturma veya video kod çözme ile senkronize etmek mümkün değildir.
  • Bu composable, diğer panellerin önünde oluşturulamaz. Bu nedenle, düzende başka paneller varsa MovePolicy kullanmamalısınız.

DRM korumalı video içeriği için yüzey ekleme

SpatialExternalSurface, DRM korumalı video akışlarının oynatılmasını da destekler. Bunu etkinleştirmek için korumalı grafik arabelleklerine işlenen güvenli bir yüzey oluşturmanız gerekir. Bu, içeriğin ekran kaydının alınmasını veya güvenli olmayan sistem bileşenleri tarafından erişilmesini engeller.

Güvenli bir yüzey oluşturmak için SpatialExternalSurfaceProtection parametresini SpatialExternalSurface composable'da SpatialExternalSurfaceProtection.Protected olarak ayarlayın. Ayrıca, lisans sunucusundan lisans alımını işlemek için Media3 Exoplayer'ı uygun DRM bilgileriyle yapılandırmanız gerekir.

Aşağıdaki örnekte, DRM ile korunan bir video akışını oynatmak için SpatialExternalSurface ve ExoPlayer öğelerinin nasıl yapılandırılacağı gösterilmektedir:

@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() }
        }
    }
}

Kodla ilgili önemli noktalar

  • Korumalı Surface: Temel alınan Surface öğesinin DRM içeriğine uygun güvenli arabelleklerle desteklenmesi için surfaceProtection = SpatialExternalSurfaceProtection.ProtectedSpatialExternalSurface ayarının etkinleştirilmesi gerekir.
  • DRM Yapılandırması: MediaItem öğesini DRM şeması (örneğin, C.WIDEVINE_UUID) ve lisans sunucunuzun URI'si ile yapılandırmanız gerekir. ExoPlayer, DRM oturumunu yönetmek için bu bilgileri kullanır.
  • Güvenli İçerik: Korunan bir yüzeyde oluşturma işlemi yapılırken video içeriği, içerik lisanslama koşullarını karşılamaya yardımcı olan güvenli bir yolda kod çözülerek görüntülenir. Bu, içeriğin ekran görüntülerinde görünmesini de engeller.

Diğer mekansal kullanıcı arayüzü bileşenlerini ekleme

Uzamsal kullanıcı arayüzü bileşenleri, uygulamanızın kullanıcı arayüzü hiyerarşisinde herhangi bir yere yerleştirilebilir. Bu öğeler 2D kullanıcı arayüzünüzde yeniden kullanılabilir ve uzamsal özellikleri yalnızca uzamsal özellikler etkinleştirildiğinde görünür. Bu sayede, kodunuzu iki kez yazmanıza gerek kalmadan menülere, iletişim kutularına ve diğer bileşenlere yükseklik ekleyebilirsiniz. Bu öğeleri nasıl kullanacağınızı daha iyi anlamak için aşağıdaki uzamsal kullanıcı arayüzü örneklerine göz atın.

Kullanıcı arayüzü bileşeni

Uzamsallaştırma etkinleştirildiğinde

2D ortamda

SpatialDialog

Panel, yükseltilmiş bir iletişim kutusu göstermek için z derinliğinde biraz geriye itilir.

2D'ye geri döner Dialog.

SpatialPopup

Panel, yükseltilmiş bir pop-up göstermek için z derinliğinde biraz geriye itilir.

2D Popup'ye geri döner.

SpatialElevation

SpatialElevationLevel, yüksekliği ekleyecek şekilde ayarlanabilir.

Mekansal yükseklik içermeyen gösterimler.

SpatialDialog

Bu, kısa bir gecikmenin ardından açılan iletişim kutusuna bir örnektir. SpatialDialog kullanıldığında iletişim kutusu, uzamsal panel ile aynı z derinliğinde görünür ve uzamsallaştırma etkinleştirildiğinde panel 125 dp geriye itilir. SpatialDialog, uzamsallaştırma etkin olmadığında da kullanılabilir. Bu durumda SpatialDialog, 2D karşılığı olan Dialog'ye geri döner.

@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")
                }
            }
        }
    }
}

Kodla ilgili önemli noktalar

Özel paneller ve düzenler oluşturma

XR için Compose tarafından desteklenmeyen özel paneller oluşturmak üzere PanelEntity örnekleriyle ve sahne grafiğiyle doğrudan SceneCore API'lerini kullanarak çalışabilirsiniz.

Yörünge uydularını uzamsal panellere ve düzenlere sabitleme

Bir yörüngeyi SpatialPanels ve Compose'da tanımlanan uzamsal yerleşim bileşenlerine sabitleyebilirsiniz. Bu, SpatialRow, SpatialColumn veya SpatialBox gibi kullanıcı arayüzü öğelerinin uzamsal düzeninde bir yörünge öğesi bildirmeyi içerir. Yörünge aracı, onu bildirdiğiniz yere en yakın olan üst öğeye sabitlenir.

Yörünge aracının davranışı, onu nerede bildirdiğinize göre belirlenir:

  • SpatialPanel ile sarmalanmış bir 2D düzende (önceki kod snippet'inde gösterildiği gibi) yörünge aracı bu SpatialPanel öğesine sabitlenir.
  • Subspace içinde, yörünge aracı en yakın ana varlığa bağlanır. Bu varlık, yörünge aracının bildirildiği uzamsal düzendir.

Aşağıdaki örnekte, bir yörünge aracının uzamsal bir satıra nasıl sabitleneceği gösterilmektedir:

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)
            )
        }
    }
}

Kodla ilgili önemli noktalar

  • Bir yörüngeyi 2D düzenin dışında bildirdiğinizde yörünge, en yakın ana varlık varlığına sabitlenir. Bu durumda, yörünge aracı, bildirildiği SpatialRow öğesinin üst kısmına sabitlenir.
  • SpatialRow, SpatialColumn, SpatialBox gibi mekansal düzenlerin tümü, içeriksiz öğelerle ilişkilidir. Bu nedenle, uzamsal düzende belirtilen bir yörünge aracı bu düzene sabitlenir.

Aşağıdaki kaynakları da incelemenizi öneririz: