Jetpack Compose na potrzeby XR umożliwia deklaratywne tworzenie interfejsu przestrzennego i układu za pomocą znanych koncepcji Compose, takich jak wiersze i kolumny. Dzięki temu możesz rozszerzyć istniejący interfejs Androida na przestrzeń 3D lub tworzyć zupełnie nowe aplikacje 3D z elementami immersyjnymi.
Jeśli chcesz przestrzennie zlokalizować istniejącą aplikację opartą na widokach Androida, masz kilka opcji programowania. Możesz używać interfejsów API interoperacyjności, korzystać jednocześnie z Compose i widoków lub pracować bezpośrednio z biblioteką SceneCore. Więcej informacji znajdziesz w naszym przewodniku po pracy z widokami.
Informacje o podprzestrzeniach i komponentach przestrzennych
Podczas pisania aplikacji na Androida XR ważne jest, aby zrozumieć koncepcje podprzestrzeni i składników przestrzennych.
Informacje o podprzestrzeni
Podczas tworzenia aplikacji na Androida XR musisz dodać do niej lub do układu Subspace
. 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 domowej lub na urządzeniach innych niż XR każdy kod w tej podprzestrzeni jest ignorowany.
Podprzestrzeń możesz utworzyć na 2 sposoby:
Subspace
: ten komponent można umieścić w dowolnym miejscu w hierarchii interfejsu aplikacji, co pozwala zachować układy interfejsu 2D i przestrzennego bez utraty kontekstu między plikami. Ułatwia to udostępnianie elementów takich jak istniejąca architektura aplikacji między XR a innymi formatami bez konieczności przenoszenia stanu przez całe drzewo interfejsu lub przebudowywania aplikacji.ApplicationSubspace
: ta funkcja tworzy podprzestrzeń tylko na poziomie aplikacji i musi być umieszczona na najwyższym poziomie w hierarchii przestrzennego interfejsu użytkownika aplikacji.ApplicationSubspace
renderuje treści przestrzenne z opcjonalnymVolumeConstraints
. W przeciwieństwie do elementuSubspace
elementApplicationSubspace
nie może być zagnieżdżony w innym elemencieSubspace
aniApplicationSubspace
.
Więcej informacji znajdziesz w artykule Dodawanie podprzestrzeni do aplikacji.
Informacje o komponentach przestrzennych
Komponenty Subspace: te komponenty mogą być renderowane tylko w przestrzeni podrzędnej.
Przed umieszczeniem w układzie 2D muszą być zamknięte w tagach Subspace
lub setSubspaceContent()
. SubspaceModifier
umożliwia dodawanie atrybutów, takich jak głębokość, przesunięcie i pozycjonowanie, do funkcji kompozycyjnych podprzestrzeni.
Inne komponenty przestrzenne nie wymagają wywoływania w podprzestrzeni. Składają się one z tradycyjnych elementów 2D umieszczonych w kontenerze przestrzennym. Jeśli są zdefiniowane dla obu typów, można ich używać w układach 2D i 3D. Jeśli przestrzenność nie jest włączona, funkcje przestrzenne zostaną zignorowane i zastąpione odpowiednikami 2D.
Tworzenie panelu przestrzennego
SpatialPanel
to komponent przestrzenny, który umożliwia wyświetlanie treści aplikacji, np. odtwarzanie filmów, wyświetlanie zdjęć lub innych treści w panelu przestrzennym.
Za pomocą parametru SubspaceModifier
możesz zmienić rozmiar, działanie i pozycję panelu przestrzennego, jak pokazano w tym przykładzie.
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 ) } }
Najważniejsze informacje o kodzie
- Ponieważ interfejsy API
SpatialPanel
są funkcjami kompozycyjnymi przestrzeni podrzędnej, musisz wywoływać je w funkcjiSubspace
. Wywołanie ich poza podprzestrzenią powoduje zgłoszenie wyjątku. - Rozmiar elementu
SpatialPanel
został określony za pomocą specyfikacjiheight
iwidth
w elemencieSubspaceModifier
. Pominięcie tych specyfikacji pozwala określić rozmiar panelu na podstawie wymiarów jego zawartości. - Zezwalaj użytkownikom na zmianę rozmiaru lub przenoszenie panelu, dodając modyfikatory
movable
lubresizable
. - Szczegółowe informacje o rozmiarach i położeniu znajdziesz w naszych wskazówkach dotyczących projektowania paneli przestrzennych. Więcej informacji o implementacji kodu znajdziesz w naszej dokumentacji referencyjnej.
Jak działa modyfikator ruchomej podprzestrzeni
Gdy użytkownik odsuwa panel od siebie, modyfikator przestrzeni ruchomej domyślnie skaluje go w podobny sposób, jak system zmienia rozmiar paneli w przestrzeni domowej. Wszystkie treści dla dzieci dziedziczą to zachowanie. Aby wyłączyć tę funkcję, ustaw parametr scaleWithDistance
na false
.
Tworzenie satelity
Orbiter to przestrzenny komponent interfejsu. Jest przeznaczony do dołączania do odpowiedniego panelu przestrzennego, układu lub innego elementu. Orbiter zwykle zawiera elementy nawigacyjne i kontekstowe związane z podmiotem, do którego jest przypięty. Jeśli na przykład utworzysz panel przestrzenny do wyświetlania treści wideo, możesz dodać elementy sterujące odtwarzaniem wideo w orbiterze.
Jak pokazano w przykładzie poniżej, wywołaj orbiter w układzie 2D w 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 konfiguracją.
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 ) } } } }
Najważniejsze informacje o kodzie
- Orbiter to przestrzenny komponent interfejsu, więc kod można ponownie wykorzystać w układach 2D lub 3D. W układzie 2D aplikacja renderuje tylko treści w orbiterze i ignoruje sam orbiter.
- Więcej informacji o korzystaniu z orbiterów i ich projektowaniu 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ą funkcji SpatialRow
, SpatialColumn
, SpatialBox
i SpatialLayoutSpacer
.
Poniższy przykład kodu pokazuje, 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
,SpatialBox
iSpatialLayoutSpacer
to komponenty podprzestrzeni, które muszą znajdować się w podprzestrzeni.- Użyj
SubspaceModifier
, aby dostosować układ. - W przypadku układów z kilkoma panelami w rzędzie zalecamy ustawienie promienia krzywizny na 825 dp za pomocą
SubspaceModifier
, aby panele otaczały użytkownika. Szczegółowe informacje znajdziesz w wytycznych dotyczących projektowania.
Umieszczanie obiektu 3D w układzie za pomocą woluminu
Aby umieścić obiekt 3D w układzie, musisz użyć komponentu podprzestrzeni o nazwie „volume”. Oto przykład, jak to zrobić.
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) {
Dodatkowe informacje
- Więcej informacji o wczytywaniu treści 3D w przestrzeni znajdziesz w artykule Dodawanie modeli 3D do aplikacji.
Dodawanie powierzchni dla treści obrazu lub filmu
SpatialExternalSurface
to komponent podrzędny, który tworzy i zarządza Surface
, w 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 wczytać stereoskopowy film side-by-side za pomocą Media3 Exoplayera 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 wartość
StereoMode
naMono
,SideBySide
lubTopBottom
w zależności od typu renderowanych treści:Mono
: obraz lub klatka filmu składa się z jednego, identycznego obrazu wyświetlanego w obu oczach.SideBySide
: obraz lub klatka filmu zawiera parę obrazów lub klatek filmu ułożonych obok siebie, gdzie obraz lub klatka po lewej stronie przedstawia widok z lewego oka, a obraz lub klatka po prawej stronie przedstawia widok z prawego oka.TopBottom
: obraz lub klatka filmu zawiera parę obrazów lub klatek filmu ułożonych pionowo, gdzie obraz lub klatka u góry przedstawia widok z lewego oka, a obraz lub klatka u dołu przedstawia widok z prawego oka.
SpatialExternalSurface
obsługuje tylko prostokątne powierzchnie.- Ten
Surface
nie rejestruje zdarzeń wprowadzania danych. - Nie można zsynchronizować zmian
StereoMode
z renderowaniem aplikacji ani dekodowaniem wideo. - Ten komponent nie może być renderowany przed innymi panelami, więc nie należy używać modyfikatorów, które można przesuwać, jeśli w układzie są inne panele.
Dodawanie platformy dla treści wideo chronionych DRM
SpatialExternalSurface
obsługuje też odtwarzanie strumieni wideo chronionych przez DRM. Aby to zrobić, musisz utworzyć bezpieczną powierzchnię, która renderuje do chronionych buforów graficznych. Zapobiega to nagrywaniu ekranu z treściami lub uzyskiwaniu do nich dostępu przez niezabezpieczone komponenty systemu.
Aby utworzyć bezpieczną powierzchnię, ustaw parametr surfaceProtection
na wartość
SurfaceProtection.Protected
w komponencie SpatialExternalSurface
.
Musisz też skonfigurować Media3 Exoplayer, podając odpowiednie informacje o DRM, aby umożliwić pobieranie licencji z serwera licencji.
Poniższy przykład pokazuje, jak skonfigurować SpatialExternalSurface
i ExoPlayer
, aby odtwarzać strumień wideo chroniony 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
- Protected Surface: ustawienie
surfaceProtection = SurfaceProtection.Protected
wartościSpatialExternalSurface
jest niezbędne, aby warstwa bazowaSurface
była obsługiwana przez bezpieczne bufory odpowiednie dla treści DRM. - Konfiguracja DRM: musisz skonfigurować
MediaItem
ze 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 na bezpiecznej ścieżce, co pomaga spełnić wymagania licencyjne dotyczące treści. Zapobiega to również wyświetlaniu treści na zrzutach ekranu.
Dodawanie innych komponentów interfejsu przestrzennego
Komponenty interfejsu przestrzennego można umieścić w dowolnym miejscu w 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. Dzięki temu możesz dodawać do menu, okien i innych komponentów efekt podniesienia bez konieczności dwukrotnego pisania kodu. Aby lepiej zrozumieć, jak korzystać z tych elementów, zapoznaj się z poniższymi przykładami interfejsu przestrzennego.
Komponent interfejsu |
Gdy przestrzenność jest włączona |
W środowisku 2D |
---|---|---|
|
Panel lekko się cofnie w głąb, aby wyświetlić okno dialogowe. |
Wracasz do widoku 2D |
|
Panel lekko się cofnie w głąb, aby wyświetlić wyskakujące okienko. |
Wracasz do 2D |
|
|
Programy bez przestrzennego podniesienia. |
SpatialDialog
To jest przykład okna, które otwiera się po krótkim opóźnieniu. Gdy używana jest wartość
SpatialDialog
, okno dialogowe pojawia się na tej samej głębokości z jak panel przestrzenny, a panel jest cofany 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 2-wymiarowego odpowiednika, 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 znacznika
SpatialDialog
. Używanie znacznikówSpatialPopup
iSpatialElevation
jest bardzo podobne. Więcej informacji znajdziesz w dokumentacji interfejsu API.
Tworzenie paneli i układów niestandardowych
Aby tworzyć niestandardowe panele, które nie są obsługiwane przez Compose for XR, możesz pracować bezpośrednio z instancjami PanelEntity
i grafem sceny za pomocą interfejsów API SceneCore
.
Przywiązywanie orbiterów do układów przestrzennych i innych elementów
Orbiter możesz przypiąć do dowolnego elementu zadeklarowanego w Compose. Obejmuje to zadeklarowanie orbitera w układzie przestrzennym elementów interfejsu, takich jak SpatialRow
, SpatialColumn
lub SpatialBox
. Orbiter jest przytwierdzony do najbliższego elementu nadrzędnego, w którego pobliżu został zadeklarowany.
Zachowanie orbitera zależy od tego, gdzie go zadeklarujesz:
- W układzie 2D zawartym w elemencie
SpatialPanel
(jak pokazano we fragmencie poprzedniego kodu) orbiter jest do niego przytwierdzony.SpatialPanel
- W
Subspace
orbiter jest przytwierdzony do najbliższego elementu nadrzędnego, czyli układu przestrzennego, w którym jest zadeklarowany.
Poniższy przykład pokazuje, jak przytwierdzić 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 element orbiter poza układem 2D, zostanie on przytwierdzony do najbliższego elementu nadrzędnego. W takim przypadku orbiter jest przytwierdzony do górnej części
SpatialRow
, w którym jest zadeklarowany. - Układy przestrzenne, takie jak
SpatialRow
,SpatialColumn
iSpatialBox
, mają powiązane z nimi jednostki bez treści. Dlatego orbiter zadeklarowany w układzie przestrzennym jest do niego przypisany.
Zobacz również
- Dodawanie modeli 3D do aplikacji
- Tworzenie interfejsu aplikacji opartych na widokach Androida
- Wdrażanie Material Design w przypadku XR