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

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 bildirimsel olarak oluşturabilirsiniz. Bu sayede, mevcut Android kullanıcı arayüzünüzü 3D alana 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ılavuzumuzu inceleyin.

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üzene Subspace 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. Evrensel Alan'da veya XR olmayan cihazlarda, bu alt uzaydaki tüm kodlar yoksayılır.

Alt alan oluşturmanın iki yolu vardır:

  • Subspace: Bu composable, uygulamanızın kullanıcı arayüzü hiyerarşisinde herhangi bir yere yerleştirilebilir. Böylece, dosyalar arasındaki bağlamı kaybetmeden 2D ve uzamsal kullanıcı arayüzü düzenlerini koruyabilirsiniz. Bu sayede, durumunuzu tüm kullanıcı arayüzü ağınızda yükseltmenize veya uygulamanızın mimarisini yeniden tasarlamanıza gerek kalmadan, XR ile diğer form faktörleri arasında mevcut uygulama mimarisi gibi öğeleri paylaşmak kolaylaşır.
  • ApplicationSubspace: Bu işlev yalnızca uygulama düzeyinde alt alan oluşturur ve uygulamanızın uzamsal kullanıcı arayüzü hiyerarşisinde en üst düzeye yerleştirilmelidir. ApplicationSubspace, isteğe bağlı VolumeConstraints ile uzamsal içerik oluşturur. Subspace'ın aksine, ApplicationSubspace başka bir Subspace veya ApplicationSubspace içine yerleştirilemez.

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

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

Alt alan composable'ları: Bu bileşenler yalnızca bir alt alanda oluşturulabilir. 2D düzene yerleştirilmeden önce Subspace veya setSubspaceContent() içine alınmaları gerekir. 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, birleştirilebilir bir alt alan bileşenidir. Örneğin, video oynatma, sabit resimler veya başka herhangi bir içeriği bir uzamsal 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)
            .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
        )
    }
}

Kodla ilgili önemli noktalar

  • SpatialPanel API'leri alt alan composable'ları olduğundan bunları Subspace içinde çağırmanız gerekir. Alt alan dışında çağrıldıklarında istisna oluştururlar.
  • 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 veya resizable değiştiricilerini ekleyerek kullanıcının paneli yeniden boyutlandırmasına veya taşımasına izin verin.
  • Boyutlandırma ve konumlandırma ile ilgili ayrıntılar için uzamsal panel tasarım yönergelerimizi inceleyin. Kod uygulamasıyla ilgili daha fazla bilgi için referans dokümanlarımızı inceleyin.

Taşınabilir alt alan değiştiricinin işleyiş şekli

Bir kullanıcı paneli kendisinden uzaklaştırdığında, varsayılan olarak taşınabilir bir alt alan değiştirici, paneli ana alanda 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 scaleWithDistance parametresini false olarak ayarlayın.

Yörünge aracı oluşturma

Orbiter, uzamsal bir kullanıcı arayüzü bileşenidir. İlgili bir mekansal panele, düzene veya başka bir öğeye eklenmek üzere tasarlanmıştır. Bir yörünge öğesi genellikle bağlı olduğu öğeyle ilgili gezinme ve bağlamsal işlem öğeleri içerir. Örneğin, video içeriğini göstermek için bir uzamsal panel oluşturduysanız bir orbiter'ın içine video oynatma kontrolleri ekleyebilirsiniz.

Yörünge aracı örneği

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

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

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üngeleri kullanma ve tasarlama 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 SpatialLayoutSpacer 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

Düzeninize 3D nesne yerleştirmek için hacim kullanma

Düzeninize 3D nesne yerleştirmek için hacim adı verilen bir alt alan composable'ı kullanmanız gerekir. Bunu nasıl yapabileceğinize dair bir örneği aşağıda bulabilirsiniz.

Bir düzendeki 3D nesne örneği

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

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 uzaydır. SpatialExternalSurface, stereoskopik veya monoskopik içeriği destekler.

Bu örnekte, Media3 Exoplayer ve SpatialExternalSurface kullanılarak yan yana stereo 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 taşınabilir değiştiriciler kullanmamalısınız.

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

SpatialExternalSurface, DRM ile korunan 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 SpatialExternalSurface composable'da surfaceProtection parametresini SurfaceProtection.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ı Yüzey: surfaceProtection = SurfaceProtection.ProtectedSpatialExternalSurface ayarının SpatialExternalSurface olarak belirlenmesi, temel alınan Surface öğesinin DRM içeriğine uygun güvenli arabelleklerle desteklenmesi için gereklidir.
  • 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 uzamsal 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 mekansal özellikleri yalnızca mekansal ö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.

2 boyutlu 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 düzenlere ve diğer öğelere sabitleme

Bir yörüngeyi Compose'da belirtilen herhangi bir öğeye 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ı, üst öğeyi, beyan ettiğiniz yere en yakın olacak şekilde sabitler.

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 üst öğeye bağlanır. Bu öğe, yörünge aracının bildirildiği uzamsal düzendir.

Aşağıdaki örnekte, bir uyduyu uzamsal bir satıra nasıl sabitleyeceğiniz 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 üst öğe varlığına sabitlenir. Bu durumda, yörünge aracı, bildirildiği SpatialRow öğesinin üst kısmına sabitlenir.
  • SpatialRow, SpatialColumn, SpatialBox gibi uzamsal düzenlerin tümü, kendileriyle ilişkili içeriksiz öğeler içerir. Bu nedenle, uzamsal düzende belirtilen bir yörünge aracı bu düzene sabitlenir.

Ayrıca bkz.