Dzięki Jetpack Compose for XR możesz deklaratywnie tworzyć przestrzenny interfejs i układ za pomocą znanych koncepcji Compose, takich jak wiersze i kolumny. Pozwala to rozszerzyć istniejący interfejs Androida na przestrzeń 3D lub tworzyć zupełnie nowe, wciągające aplikacje 3D.
Jeśli tworzysz aplikację opartą na widokach Androida, możesz skorzystać z kilku opcji. Możesz użyć interfejsów API interoperacyjności, korzystać z Compose i widoków razem lub pracować bezpośrednio z biblioteką SceneCore. Więcej informacji znajdziesz w naszym przewodniku dotyczącym pracy z widokami.
Informacje o podprzestrzeniach i komponentach przestrzennych
Podczas pisania aplikacji na Androida XR ważne jest, aby zrozumieć koncepcje podprzestrzeni i komponentów przestrzennych.
Informacje o podprzestrzeni
Podczas tworzenia aplikacji na Androida XR musisz dodać do niej lub do układu podprzestrzeń. Podprzestrzeń to część przestrzeni 3D w aplikacji, w której możesz umieszczać treści 3D, tworzyć układy 3D i dodawać głębię do treści 2D. Podprzestrzeń jest renderowana tylko wtedy, gdy włączona jest przestrzenność. W przestrzeni głównej lub na urządzeniach innych niż XR kod w tej podprzestrzeni jest ignorowany.
Podprzestrzeń można utworzyć na kilka sposobów:
Subspace: ten komponent tworzy nową, niezależną hierarchię przestrzennego interfejsu. Nie dziedziczy pozycji przestrzennej, orientacji ani skali żadnej nadrzędnej podprzestrzeniSubspace, w której jest zagnieżdżony.Subspacejest automatycznie powiązana z zalecanym przez system polem treści.PlanarEmbeddedSubspace: ten komponent można umieścić w hierarchii interfejsu aplikacji, co pozwala zachować układy interfejsu 2D i przestrzennego.PlanarEmbeddedSubspaceuwzględnia ograniczenia i pozycjonowanie elementu nadrzędnego. Umieszczone w nim treści 3D są następnie pozycjonowane względem tego obszaru zdefiniowanego w 2D.
Więcej informacji znajdziesz w artykule Dodawanie podprzestrzeni do aplikacji.
Informacje o komponentach przestrzennych
Komponenty podprzestrzeni: te komponenty można renderować tylko w podprzestrzeni.
Przed umieszczeniem w układzie 2D muszą być zamknięte w komponencie Subspace.
A SubspaceModifier umożliwia dodawanie do komponentów podprzestrzeni atrybutów takich jak głębia, przesunięcie i
pozycjonowanie.
Inne komponenty przestrzenne nie wymagają wywoływania w podprzestrzeni. Składają się z konwencjonalnych elementów 2D opakowanych w kontener przestrzenny. Jeśli te elementy są zdefiniowane zarówno dla układów 2D, jak i 3D, można ich używać w obu tych układach. Gdy przestrzenność nie jest włączona, ich funkcje przestrzenne są ignorowane i wracają do swoich odpowiedników 2D.
Tworzenie panelu przestrzennego
SpatialPanel to komponent podprzestrzeni, który umożliwia wyświetlanie treści aplikacji. Możesz na przykład wyświetlać w nim odtwarzanie wideo, obrazy statyczne lub inne treści.

Za pomocą SubspaceModifier możesz zmieniać rozmiar, zachowanie i pozycjonowanie panelu przestrzennego, jak pokazano w tym przykładzie.
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 ) } }
Najważniejsze informacje o kodzie
- Ponieważ
SpatialPanelinterfejsy API są komponentami podprzestrzeni, musisz wywołać je w komponencieSubspace. Wywołanie ich poza podprzestrzenią powoduje zgłoszenie wyjątku. - Rozmiar komponentu
SpatialPanelzostał ustawiony za pomocą specyfikacjiheightiwidthw komponencieSubspaceModifier. Pominięcie tych specyfikacji powoduje, że rozmiar panelu jest określany na podstawie wymiarów jego zawartości. - Aby umożliwić użytkownikowi przesuwanie panelu, dodaj modyfikator podprzestrzeni
movable. - Aby umożliwić użytkownikowi zmianę rozmiaru panelu, dodaj
resizablemodyfikator podprzestrzeni. - Szczegółowe informacje o rozmiarach i pozycjonowaniu znajdziesz w naszych wskazówkach dotyczących projektowania paneli przestrzennych. Więcej informacji o implementacji kodu znajdziesz w naszej dokumentacji.
Jak działa modyfikator movable
Gdy użytkownik odsuwa panel od siebie, modyfikator movable domyślnie
skaluje go w podobny sposób jak system zmienia rozmiar paneli w
przestrzeni głównej. To zachowanie dziedziczą wszystkie treści podrzędne. Aby wyłączyć tę funkcję, ustaw parametr shouldScaleWithDistance na false.
Tworzenie orbitera
Orbiter to komponent przestrzennego interfejsu. Jest on przeznaczony do dołączania do
odpowiedniego panelu przestrzennego lub komponentu układu przestrzennego, takiego jak SpatialColumn, SpatialRow czy SpatialBox. Orbiter zwykle zawiera elementy nawigacji i działania kontekstowe związane z encją, do której jest zakotwiczony. Jeśli na przykład utworzysz panel przestrzenny do wyświetlania treści wideo, możesz dodać do orbitera elementy sterujące odtwarzaniem wideo.

Jak pokazano w tym przykładzie, wywołaj orbiter w układzie 2D w komponencie SpatialPanel, aby opakować elementy sterujące użytkownika, takie jak nawigacja. Spowoduje to wyodrębnienie ich z układu 2D i dołączenie do panelu przestrzennego zgodnie z Twoją konfiguracją.
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 ) } } } }
Najważniejsze informacje o kodzie
- Ponieważ orbitery są komponentami przestrzennego interfejsu, kod można ponownie wykorzystać w układach 2D i 3D. W układzie 2D aplikacja renderuje tylko treści w orbiterze i ignoruje sam orbiter.
- Więcej informacji o tym, jak używać i projektować orbitery, znajdziesz w naszych wskazówkach dotyczących projektowania.
Dodawanie wielu paneli przestrzennych do układu przestrzennego
Możesz utworzyć wiele paneli przestrzennych i umieścić je w układzie przestrzennym
za pomocą SpatialRow, SpatialColumn, SpatialBox i
SpatialSpacer.

W tym przykładzie kodu pokazujemy, jak to zrobić.
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 ) } }
Najważniejsze informacje o kodzie
SpatialRow,SpatialColumn,SpatialBoxiSpatialSpacerto komponenty podprzestrzeni, które muszą być umieszczone w podprzestrzeni.- Aby dostosować układ, użyj komponentu
SubspaceModifier. - W przypadku układów z wieloma panelami w wierszu zalecamy ustawienie promienia krzywizny na 825 dp za pomocą komponentu
SubspaceModifier, aby panele otaczały użytkownika. Szczegółowe informacje znajdziesz w naszych wskazówkach dotyczących projektowania.
Dodawanie obiektu 3D do układu za pomocą komponentu SpatialGltfModel
Android XR obsługuje format glTF dla modeli 3D, zwykle zapisywanych jako pliki
.glb. Aby dodać te obiekty do układu, użyj komponentu
SpatialGltfModel. Ten interfejs API upraszcza proces wczytywania komponentów i zarządzania ich stanem.
Aby wyświetlić model, najpierw zdefiniuj jego źródło i stan za pomocą
rememberSpatialGltfModelState. Modele możesz wczytywać
z folderu assets aplikacji, z URI lub z
raw data.
val modelState = rememberSpatialGltfModelState( source = SpatialGltfModelSource.fromPath( Paths.get("models/model_name.glb") ) )
Po zdefiniowaniu stanu użyj komponentu SpatialGltfModel, aby wyrenderować go w podprzestrzeni.
SpatialGltfModel(state = modelState, modifier = SubspaceModifier)
Najważniejsze informacje o kodzie
- Wczytywanie asynchroniczne: model jest wczytywany asynchronicznie. Podczas początkowej kompozycji jego rozmiar wewnętrzny może wynosić zero. Układ jest ponownie mierzony, gdy model jest gotowy.
- Kontrolowanie stanu: użyj
SpatialGltfModelState.status, aby sprawdzić stan wczytywania lub sterować animacjami. - Rozmiar i skalowanie: domyślnie rozmiar układu jest zgodny z ramką ograniczającą komponentu. Możesz to zmienić za pomocą komponentu
SubspaceModifier.size, aby równomiernie skalować model tak, aby mieścił się w określonych granicach.
Używanie komponentu SceneCoreEntity do umieszczania encji w układzie
Komponent SceneCoreEntity łączy biblioteki Jetpack
SceneCore i Compose for XR, dzięki czemu możesz używać
encji utworzonych za pomocą SceneCore w układach Compose. Pozwala to tworzyć encje niższego poziomu i komponenty niestandardowe, a jednocześnie umożliwia Compose zmianę rozmiaru, położenia, rodzica, dodawanie elementów podrzędnych i stosowanie modyfikatorów do tych encji.
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. } }
Najważniejsze informacje o kodzie
- Blok fabryczny: w bloku fabrycznym inicjujesz podstawową encję
SceneCore. - Blok aktualizacji: użyj bloku aktualizacji, aby modyfikować właściwości encji w odpowiedzi na zmiany stanu Compose.
- Dopasowanie rozmiaru: komponent
sizeAdapterprzekazuje wymiary encji do systemu układu Compose.
Dodatkowe informacje
- Aby lepiej zrozumieć, jak wczytywać treści 3D w komponencie
SceneCoreEntity, przeczytaj artykuł Dodawanie modeli 3D do aplikacji.
Dodawanie powierzchni dla treści graficznych lub wideo
SpatialExternalSurface to komponent podprzestrzeni, który tworzy i
zarządza komponentem Surface, na którym aplikacja może rysować treści, takie jak obraz lub film. SpatialExternalSurface obsługuje treści stereoskopowe i monoskopowe.
Ten przykład pokazuje, jak wczytywać stereoskopowe wideo obok siebie za pomocą
Media3 Exoplayer i 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() } } } }
Najważniejsze informacje o kodzie
- Ustaw
StereoModenaMono,SideBySide, lubTopBottomw zależności od typu renderowanych treści:Mono: klatka obrazu lub filmu składa się z pojedynczego, identycznego obrazu wyświetlanego na obu oczach.SideBySide: klatka obrazu lub filmu zawiera parę obrazów lub klatek wideo ułożonych obok siebie, gdzie obraz lub klatka po lewej stronie reprezentuje widok lewego oka, a obraz lub klatka po prawej stronie – widok prawego oka.TopBottom: klatka obrazu lub filmu zawiera parę obrazów lub klatek wideo ułożonych pionowo, gdzie obraz lub klatka u góry reprezentuje widok lewego oka, a obraz lub klatka u dołu – widok prawego oka.
SpatialExternalSurfaceobsługuje tylko powierzchnie prostokątne.- Ten
Surfacenie przechwytuje zdarzeń wejściowych. - Nie można zsynchronizować
StereoModezmian z renderowaniem aplikacji ani dekodowaniem wideo. - Ten komponent nie może być renderowany przed innymi panelami, więc nie należy używać komponentu
MovePolicy, jeśli w układzie znajdują się inne panele.
Dodawanie powierzchni dla treści wideo chronionych przez DRM
SpatialExternalSurface obsługuje też odtwarzanie strumieni wideo chronionych przez DRM. Aby to włączyć, musisz utworzyć bezpieczną powierzchnię, która renderuje do chronionych buforów graficznych. Uniemożliwia to nagrywanie ekranu i dostęp do treści przez niezabezpieczone komponenty systemu.
Aby utworzyć bezpieczną powierzchnię, ustaw parametr surfaceProtection na
SurfaceProtection.Protected w komponencie SpatialExternalSurface.
Dodatkowo musisz skonfigurować Media3 Exoplayer z odpowiednimi informacjami o DRM, aby obsługiwać pobieranie licencji z serwera licencji.
Ten przykład pokazuje, jak skonfigurować SpatialExternalSurface i ExoPlayer do odtwarzania strumienia wideo chronionego przez 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() } } } }
Najważniejsze informacje o kodzie
- Chroniona powierzchnia: ustawienie
surfaceProtection = SurfaceProtection.Protectedw komponencieSpatialExternalSurfacejest niezbędne, aby podstawowy komponentSurfacebył obsługiwany przez bezpieczne bufory odpowiednie dla treści DRM. - Konfiguracja DRM: musisz skonfigurować
MediaItemze schematem DRM (np.C.WIDEVINE_UUID) i adresem URI serwera licencji. ExoPlayer używa tych informacji do zarządzania sesją DRM. - Bezpieczne treści: podczas renderowania na chronionej powierzchni treści wideo są dekodowane i wyświetlane w bezpieczny sposób, co pomaga spełnić wymagania dotyczące licencjonowania treści. Uniemożliwia to też wyświetlanie treści na zrzutach ekranu.
Dodawanie innych komponentów przestrzennego interfejsu
Komponenty przestrzennego interfejsu można umieszczać w dowolnym miejscu hierarchii interfejsu aplikacji. Te elementy można ponownie wykorzystać w interfejsie 2D, a ich atrybuty przestrzenne będą widoczne tylko wtedy, gdy włączone są funkcje przestrzenne. Pozwala to dodawać wysokość do menu, okien i innych komponentów bez konieczności dwukrotnego pisania kodu. Aby lepiej zrozumieć, jak używać tych elementów, zapoznaj się z tymi przykładami przestrzennego interfejsu.
Komponent interfejsu |
Gdy włączona jest przestrzenność |
W środowisku 2D |
|---|---|---|
|
Panel zostanie lekko przesunięty do tyłu w głąb osi Z, aby wyświetlić podniesione okno. |
Wracamy do komponentu w 2D |
|
Panel zostanie lekko przesunięty do tyłu w głąb osi Z, aby wyświetlić podniesione okno. |
Wracamy do komponentu w 2D |
|
Aby dodać wysokość, możesz ustawić |
Wyświetla się bez wysokości przestrzennej. |
SpatialDialog
To jest przykład okna, które otwiera się po krótkim opóźnieniu. Gdy
SpatialDialog jest używany, okno pojawia się na tej samej głębokości osi Z co
panel przestrzenny, a panel jest przesuwany do tyłu o 125 dp, gdy włączona jest
przestrzenność. SpatialDialog można też używać, gdy przestrzenność nie jest włączona. W
takim przypadku SpatialDialog wraca do swojego odpowiednika 2D, czyli 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") } } } } }
Najważniejsze informacje o kodzie
- To jest przykład komponentu
SpatialDialog. UżywanieSpatialPopupiSpatialElevationjest bardzo podobne. Więcej informacji znajdziesz w dokumentacji API.
Tworzenie paneli i układów niestandardowych
Aby tworzyć panele niestandardowe, które nie są obsługiwane przez Compose for XR, możesz pracować bezpośrednio z PanelEntity instancjami i wykresem sceny za pomocą SceneCore interfejsów API.
Kotwiczenie orbiterów do paneli i układów przestrzennych
Możesz zakotwiczyć orbiter do komponentów SpatialPanels i układu przestrzennego zadeklarowanych w Compose. W tym celu zadeklaruj orbiter w układzie przestrzennym elementów interfejsu, takich jak SpatialRow, SpatialColumn czy SpatialBox. Orbiter kotwiczy się do najbliższego elementu nadrzędnego w miejscu, w którym został zadeklarowany.
Zachowanie orbitera zależy od miejsca, w którym go zadeklarujesz:
- W układzie 2D opakowanym w
SpatialPanel(jak pokazano w poprzednim fragmencie kodu) orbiter kotwiczy się do tegoSpatialPanel. - W komponencie
Subspaceorbiter kotwiczy się do najbliższej encji nadrzędnej, czyli do układu przestrzennego, w którym jest zadeklarowany.
Ten przykład pokazuje, jak zakotwiczyć orbiter do wiersza przestrzennego:
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) ) } } }
Najważniejsze informacje o kodzie
- Gdy zadeklarujesz orbiter poza układem 2D, orbiter kotwiczy się do najbliższej encji nadrzędnej. W tym przypadku orbiter kotwiczy się u góry komponentu
SpatialRow, w którym jest zadeklarowany. - Układy przestrzenne, takie jak
SpatialRow,SpatialColumniSpatialBox, mają powiązane z nimi encje bez treści. Dlatego orbiter zadeklarowany w układzie przestrzennym kotwiczy się do tego układu.
Zobacz też
- Dodawanie modeli 3D do aplikacji
- Tworzenie interfejsu aplikacji opartych na widokach Androida
- Implementowanie Material Design dla XR