Dengan Jetpack Compose untuk XR, Anda dapat membangun UI dan tata letak spasial secara deklaratif menggunakan konsep Compose yang sudah dikenal seperti baris dan kolom. Hal ini memungkinkan Anda memperluas UI Android yang ada ke ruang 3D atau membangun aplikasi 3D imersif yang benar-benar baru.
Jika Anda melakukan spasialisasi aplikasi berbasis Android View yang sudah ada, Anda memiliki beberapa opsi pengembangan. Anda dapat menggunakan API interoperabilitas, menggunakan Compose dan View secara bersamaan, atau bekerja langsung dengan library SceneCore. Lihat panduan kami untuk menggunakan tampilan untuk mengetahui detail selengkapnya.
Tentang subruang dan komponen yang dispatialisasi
Saat menulis aplikasi untuk Android XR, penting untuk memahami konsep subruang dan komponen spasial.
Tentang subruang
Saat mengembangkan untuk Android XR, Anda harus menambahkan Subspace
ke aplikasi
atau tata letak. Subruang adalah partisi ruang 3D dalam aplikasi Anda tempat Anda dapat menempatkan konten 3D, membangun tata letak 3D, dan menambahkan kedalaman pada konten 2D. Subruang hanya dirender jika spasialisasi diaktifkan. Di Ruang Rumah atau di perangkat non-XR, kode apa pun dalam subruang tersebut akan diabaikan.
Ada dua cara untuk membuat subruang:
Subspace
: Composable ini dapat ditempatkan di mana saja dalam hierarki UI aplikasi Anda, sehingga Anda dapat mempertahankan tata letak untuk UI 2D dan spasial tanpa kehilangan konteks antar-file. Hal ini mempermudah berbagi hal-hal seperti arsitektur aplikasi yang ada antara XR dan faktor bentuk lainnya tanpa perlu mengangkat status melalui seluruh hierarki UI atau mendesain ulang aplikasi.ApplicationSubspace
: Fungsi ini hanya membuat subruang tingkat aplikasi dan harus ditempatkan di tingkat paling atas dalam hierarki UI spasial aplikasi Anda.ApplicationSubspace
merender konten spasial denganVolumeConstraints
opsional. Tidak sepertiSubspace
,ApplicationSubspace
tidak dapat disarangkan dalamSubspace
atauApplicationSubspace
lainnya.
Untuk mengetahui informasi selengkapnya, lihat Menambahkan subruang ke aplikasi Anda.
Tentang komponen yang dispatialisasi
Composable subruang: Komponen ini hanya dapat dirender di subruang.
Elemen ini harus disertakan dalam Subspace
atau setSubspaceContent()
sebelum
ditempatkan dalam tata letak 2D. SubspaceModifier
memungkinkan Anda menambahkan atribut
seperti kedalaman, offset, dan pemosisian ke composable subruang Anda.
Komponen spasialisasi lainnya tidak perlu dipanggil di dalam subruang. Elemen ini terdiri dari elemen 2D konvensional yang digabungkan dalam penampung spasial. Elemen ini dapat digunakan dalam tata letak 2D atau 3D jika ditentukan untuk keduanya. Jika spasialisasi tidak diaktifkan, fitur spasialnya akan diabaikan dan akan kembali ke fitur 2D-nya.
Membuat panel spasial
SpatialPanel
adalah composable subruang yang memungkinkan Anda menampilkan konten aplikasi, misalnya, Anda dapat menampilkan pemutaran video, gambar diam, atau konten lainnya di panel spasial.
Anda dapat menggunakan SubspaceModifier
untuk mengubah ukuran, perilaku, dan pemosisian panel spasial, seperti yang ditunjukkan dalam contoh berikut.
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 ) } }
Poin penting tentang kode
- Karena API
SpatialPanel
adalah composable subruang, Anda harus memanggilnya di dalamSubspace
. Memanggilnya di luar subruang akan memunculkan pengecualian. - Ukuran
SpatialPanel
telah ditetapkan menggunakan spesifikasiheight
danwidth
diSubspaceModifier
. Jika spesifikasi ini tidak disertakan, ukuran panel akan ditentukan oleh pengukuran kontennya. - Izinkan pengguna mengubah ukuran atau memindahkan panel dengan menambahkan pengubah
movable
atauresizable
. - Lihat panduan desain panel spasial kami untuk mengetahui detail tentang ukuran dan penempatan. Lihat dokumentasi referensi kami untuk mengetahui detail selengkapnya tentang penerapan kode.
Cara kerja pengubah subruang yang dapat dipindahkan
Saat pengguna memindahkan panel dari dirinya, secara default pengubah subruang yang dapat dipindahkan akan menskalakan panel dengan cara yang mirip dengan cara panel diubah ukurannya oleh sistem di ruang beranda. Semua konten turunan mewarisi perilaku ini. Untuk menonaktifkannya,
tetapkan parameter scaleWithDistance
ke false
.
Membuat pengorbit
Orbiter adalah komponen UI spasial. Objek ini dirancang untuk dilampirkan ke panel tata ruang, tata letak, atau entitas lainnya yang sesuai. Orbiter biasanya berisi item tindakan kontekstual dan navigasi yang terkait dengan entitas yang ditambatkan. Misalnya, jika Anda telah membuat panel spasial untuk menampilkan konten video, Anda dapat menambahkan kontrol pemutaran video di dalam pengorbit.
Seperti yang ditunjukkan dalam contoh berikut, panggil orbiter di dalam tata letak 2D dalam
SpatialPanel
untuk membungkus kontrol pengguna seperti navigasi. Dengan melakukannya, item akan diekstrak
dari tata letak 2D dan dilampirkan ke panel spasial sesuai dengan
konfigurasi Anda.
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 ) } } } }
Poin penting tentang kode
- Karena pengorbit adalah komponen UI spasial, kode dapat digunakan kembali dalam tata letak 2D atau 3D. Dalam tata letak 2D, aplikasi Anda hanya merender konten di dalam orbiter dan mengabaikan orbiter itu sendiri.
- Lihat panduan desain kami untuk mengetahui informasi selengkapnya tentang cara menggunakan dan mendesain pengorbit.
Menambahkan beberapa panel spasial ke tata letak spasial
Anda dapat membuat beberapa panel spasial dan menempatkannya dalam tata letak spasial
menggunakan SpatialRow
, SpatialColumn
, SpatialBox
, dan
SpatialLayoutSpacer
.
Contoh kode berikut menunjukkan cara melakukannya.
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 ) } }
Poin penting tentang kode
SpatialRow
,SpatialColumn
,SpatialBox
, danSpatialLayoutSpacer
adalah composable subruang dan harus ditempatkan dalam subruang.- Gunakan
SubspaceModifier
untuk menyesuaikan tata letak Anda. - Untuk tata letak dengan beberapa panel dalam satu baris, sebaiknya tetapkan radius
kurva 825 dp menggunakan
SubspaceModifier
sehingga panel akan mengelilingi pengguna Anda. Lihat panduan desain kami untuk mengetahui detailnya.
Menggunakan volume untuk menempatkan objek 3D dalam tata letak
Untuk menempatkan objek 3D dalam tata letak, Anda harus menggunakan composable subruang yang disebut volume. Berikut contoh cara melakukannya.
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) {
Informasi tambahan
- Lihat Menambahkan model 3D ke aplikasi Anda untuk lebih memahami cara memuat konten 3D dalam volume.
Menambahkan platform untuk konten gambar atau video
SpatialExternalSurface
adalah composable subruang yang membuat dan
mengelola Surface
tempat aplikasi Anda dapat menggambar konten, seperti
gambar atau video. SpatialExternalSurface
mendukung konten stereoskopik atau monoskopik.
Contoh ini menunjukkan cara memuat video stereoskopik berdampingan menggunakan
Media3 Exoplayer dan SpatialExternalSurface
:
@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() } } } }
Poin penting tentang kode
- Tetapkan
StereoMode
keMono
,SideBySide
, atauTopBottom
, bergantung pada jenis konten yang Anda render:Mono
: Bingkai gambar atau video terdiri dari satu gambar identik yang ditampilkan ke kedua mata.SideBySide
: Frame gambar atau video berisi sepasang gambar atau frame video yang disusun berdampingan, dengan gambar atau frame di sebelah kiri mewakili tampilan mata kiri, dan gambar atau frame di sebelah kanan mewakili tampilan mata kanan.TopBottom
: Frame gambar atau video berisi sepasang gambar atau frame video yang disusun secara vertikal, dengan gambar atau frame di bagian atas mewakili tampilan mata kiri, dan gambar atau frame di bagian bawah mewakili tampilan mata kanan.
SpatialExternalSurface
hanya mendukung platform persegi panjang.Surface
ini tidak merekam peristiwa input.- Anda tidak dapat menyinkronkan perubahan
StereoMode
dengan rendering aplikasi atau decoding video. - Composable ini tidak dapat dirender di depan panel lain, jadi Anda tidak boleh menggunakan pengubah yang dapat dipindahkan jika ada panel lain dalam tata letak.
Menambahkan platform untuk konten video yang dilindungi DRM
SpatialExternalSurface
juga mendukung pemutaran streaming video yang dilindungi DRM. Untuk mengaktifkannya, Anda harus membuat permukaan aman yang dirender ke
buffer grafis yang dilindungi. Tindakan ini mencegah konten direkam layar
atau diakses oleh komponen sistem yang tidak aman.
Untuk membuat permukaan yang aman, tetapkan parameter surfaceProtection
ke
SurfaceProtection.Protected
pada composable SpatialExternalSurface
.
Selain itu, Anda harus mengonfigurasi Media3 Exoplayer dengan informasi DRM yang sesuai untuk menangani perolehan lisensi dari server lisensi.
Contoh berikut menunjukkan cara mengonfigurasi SpatialExternalSurface
dan
ExoPlayer
untuk memutar streaming video yang dilindungi DRM:
@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() } } } }
Poin penting tentang kode
- Surface yang Dilindungi: Menetapkan
surfaceProtection = SurfaceProtection.Protected
padaSpatialExternalSurface
sangat penting agarSurface
yang mendasarinya didukung oleh buffer aman yang sesuai untuk konten DRM. - Konfigurasi DRM: Anda harus mengonfigurasi
MediaItem
dengan skema DRM (misalnya,C.WIDEVINE_UUID
) dan URI server lisensi Anda. ExoPlayer menggunakan informasi ini untuk mengelola sesi DRM. - Konten Aman: Saat merender ke platform yang dilindungi, konten video akan didekode dan ditampilkan di jalur yang aman, yang membantu memenuhi persyaratan pemberian lisensi konten. Hal ini juga mencegah konten muncul dalam screenshot.
Menambahkan komponen UI spasial lainnya
Komponen UI spasial dapat ditempatkan di mana saja dalam hierarki UI aplikasi Anda. Elemen ini dapat digunakan kembali di UI 2D Anda, dan atribut spasialnya hanya akan terlihat jika kemampuan spasial diaktifkan. Hal ini memungkinkan Anda menambahkan ketinggian pada menu, dialog, dan komponen lainnya tanpa perlu menulis kode dua kali. Lihat contoh UI spasial berikut untuk lebih memahami cara menggunakan elemen ini.
Komponen UI |
Saat spasialisasi diaktifkan |
Di lingkungan 2D |
---|---|---|
|
Panel akan sedikit didorong kembali dalam kedalaman z untuk menampilkan dialog yang ditinggikan |
Melakukan fallback ke 2D |
|
Panel akan sedikit didorong kembali dalam kedalaman z untuk menampilkan pop-up yang lebih tinggi |
Melakukan fallback ke |
|
|
Tampilan tanpa elevasi spasial. |
SpatialDialog
Ini adalah contoh dialog yang terbuka setelah penundaan singkat. Saat
SpatialDialog
digunakan, dialog akan muncul pada kedalaman z yang sama dengan panel spasial, dan panel didorong kembali sejauh 125 dp saat spasialisasi diaktifkan. SpatialDialog
juga dapat digunakan saat spasialisasi tidak diaktifkan, dalam
hal ini SpatialDialog
akan kembali ke padanannya dalam 2D, Dialog
.
@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") } } } } }
Poin penting tentang kode
- Berikut adalah contoh
SpatialDialog
. PenggunaanSpatialPopup
danSpatialElevation
sangat mirip. Lihat referensi API kami untuk mengetahui detail selengkapnya.
Membuat panel dan tata letak kustom
Untuk membuat panel kustom yang tidak didukung oleh Compose untuk XR, Anda dapat bekerja
langsung dengan instance PanelEntity
dan grafik adegan menggunakan
API SceneCore
.
Menyematkan pengorbit ke tata letak spasial dan entitas lainnya
Anda dapat menyematkan pengorbit ke entitas apa pun yang dideklarasikan di Compose. Hal ini melibatkan
deklarasi orbiter dalam tata letak spasial elemen UI seperti SpatialRow
,
SpatialColumn
, atau SpatialBox
. Pengorbit di-anchor ke entitas induk
terdekat dengan tempat Anda mendeklarasikannya.
Perilaku pengorbit ditentukan oleh tempat Anda mendeklarasikannya:
- Dalam tata letak 2D yang di-wrap dalam
SpatialPanel
(seperti yang ditunjukkan dalam cuplikan kode sebelumnya), pengorbit di-anchor keSpatialPanel
tersebut. - Dalam
Subspace
, orbiter dikaitkan ke entity induk terdekat, yaitu tata letak spasial tempat orbiter dideklarasikan.
Contoh berikut menunjukkan cara menyematkan pengorbit ke baris spasial:
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) ) } } }
Poin penting tentang kode
- Saat Anda mendeklarasikan orbiter di luar tata letak 2D, orbiter akan dikaitkan ke
entitas induk terdekatnya. Dalam hal ini, pengorbit di-anchor ke bagian atas
SpatialRow
tempat pengorbit dideklarasikan. - Tata letak spasial seperti
SpatialRow
,SpatialColumn
,SpatialBox
semuanya memiliki entitas tanpa konten yang terkait dengannya. Oleh karena itu, pengorbit yang dideklarasikan dalam tata letak spasial di-anchor ke tata letak tersebut.
Lihat juga
- Menambahkan model 3D ke aplikasi Anda
- Mengembangkan UI untuk Aplikasi berbasis View Android
- Menerapkan Desain Material untuk XR