Dzięki Jetpack Compose for XR możesz deklaratywnie tworzyć interfejs i układ przestrzenny, korzystając ze znanych koncepcji Compose, takich jak wiersze i kolumny. Dzięki temu możesz rozszerzyć istniejące interfejs użytkownika Androida o przestrzeń 3D lub tworzyć zupełnie nowe, wciągające aplikacje 3D.
Jeśli chcesz dodać obsługę dźwięku przestrzennego w istniejącej aplikacji opartej na Android Views, masz do wyboru kilka opcji. Możesz używać interfejsów API interoperacyjności, używać Compose i widoków razem lub pracować bezpośrednio z biblioteką SceneCore. Więcej informacji znajdziesz w przewodniku po widokach.
Informacje o podprzestrzeniach i komponentach przestrzennych
Podczas pisania aplikacji na Androida XR ważne jest zrozumienie koncepcji podprzestrzeni i przestrzennych komponentów.
Subspace
Podczas tworzenia aplikacji na Androida XR musisz dodać do niej podprzestrzeń lub układ. Subspace 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. Subprzestrzeń jest renderowana tylko wtedy, gdy włączona jest przestrzenność. W Home Space lub na urządzeniach bez funkcji XR każdy kod w tym podprzestrzeni jest ignorowany.
Subprzestrzeń możesz utworzyć na 2 sposoby:
setSubspaceContent()
: ta funkcja tworzy podprzestrzeń na poziomie aplikacji. Możesz go wywołać w głównej aktywności w taki sam sposób jaksetContent()
. Subprzestrzeń na poziomie aplikacji jest nieograniczona pod względem wysokości, szerokości i głębi, co zapewnia nieograniczone pole do prezentacji treści przestrzennych.Subspace
: ten komponent może być umieszczony w dowolnym miejscu w hierarchii interfejsu aplikacji, co umożliwia zachowanie układów interfejsu 2D i przestrzennego bez utraty kontekstu między plikami. Dzięki temu łatwiej jest udostępniać takie elementy, jak istniejąca architektura aplikacji między XR a innymi formatami, bez konieczności przenoszenia stanu przez całe drzewo interfejsu użytkownika ani ponownego projektowania aplikacji.
Więcej informacji znajdziesz w artykule Dodawanie podprzestrzeni do aplikacji.
Komponenty zlokalizowane
Komponenty w podprzestrzeni: te komponenty można renderować tylko w podprzestrzeni.
Przed umieszczeniem w układzie 2D muszą być one umieszczone w elementach Subspace
lub setSubspaceContent()
. SubspaceModifier
pozwala dodawać atrybuty, takie jak głębia, przesunięcie i pozycjonowanie, do komponentów w przestrzeni podrzędnej.
Inne komponenty przestrzenne nie wymagają wywołania w podprzestrzeni. Składają się one z tradycyjnych elementów 2D umieszczonych w kontenerze przestrzennym. Te elementy mogą być używane w układach 2D lub 3D, jeśli są zdefiniowane dla obu. Jeśli funkcja dźwięku przestrzennego jest wyłączona, funkcje dźwięku przestrzennego zostaną zignorowane, a dźwięk będzie odtwarzany w trybie 2D.
Tworzenie panelu przestrzennego
SpatialPanel
to komponent podprzestrzeniowy, który umożliwia wyświetlanie treści aplikacji. Na panelu przestrzennym możesz na przykład wyświetlić film, obrazy statyczne lub inne treści.
Za pomocą SubspaceModifier
możesz zmienić rozmiar, działanie i pozycjonowanie 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
- Interfejsy API
SpatialPanel
są kompozycjami w subprzestrzeni, więc musisz je wywoływać w ramachSubspace
lubsetSubspaceContent()
. Wywołanie ich poza subprzestrzenią powoduje wyjątek. - Rozmiar
SpatialPanel
został ustawiony za pomocą specyfikacjiheight
iwidth
wSubspaceModifier
. Pominięcie tych specyfikacji pozwala określić rozmiar panelu na podstawie wymiarów jego zawartości. - Umożliw użytkownikowi zmianę rozmiaru lub przeniesienie panelu, dodając modyfikatory
movable
lubresizable
. - Szczegółowe informacje o wymiarach i umieszczaniu znajdziesz w wskazówkach dotyczących projektowania paneli przestrzennych. Więcej informacji o implementacji kodu znajdziesz w dokumentacji referencyjnej.
Jak działa modyfikator przenośnego podprzestrzeni
Gdy użytkownik odsuwa panel od siebie, domyślnie modyfikator przenośnego podprzestrzeni zmienia jego rozmiar w podobny sposób, w jaki system zmienia rozmiar paneli w przestrzeni domowej. Wszystkie treści podrzędne dziedziczą to zachowanie. Aby wyłączyć tę funkcję, ustaw parametr scaleWithDistance
na false
.
Tworzenie orbitera
Orbiter to komponent UI przestrzennego. Jest on przeznaczony do dołączenia do odpowiedniego panelu przestrzennego, układu lub innego elementu. Orbiter zwykle zawiera elementy nawigacji i działania kontekstowe związane z elementem, do którego jest zamocowany. Jeśli na przykład utworzysz panel przestrzenny, aby wyświetlać treści wideo, możesz dodać elementy sterujące odtwarzaniem filmu w orbicie.
Jak pokazano w tym przykładzie, wywołaj orbiter w ramach układu 2D w komponencie SpatialPanel
, aby owinąć elementy sterujące użytkownika, takie jak nawigacja. Spowoduje to wyodrębnienie obiektów z układu 2D i ich 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 = OrbiterEdge.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
- Orbitery to komponenty UI przestrzennego, więc kod można ponownie wykorzystać w układach 2D lub 3D. W układzie 2D aplikacja renderuje tylko zawartość wewnątrz orbitera i ignoruje sam orbiter.
- Więcej informacji o używaniu i projektowaniu orbiterów znajdziesz w 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, używając SpatialRow
, SpatialColumn
, SpatialBox
i SpatialLayoutSpacer
.
Jak to zrobić, pokazuje przykładowy kod poniżej.
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ą być umieszczone w podprzestrzeni.- Aby dostosować układ, użyj
SubspaceModifier
. - W przypadku układów z wieloma panelami w rzędzie zalecamy ustawienie krzywizny o promieniu 825 dp za pomocą funkcji
SubspaceModifier
, aby panele otaczały użytkownika. Szczegółowe informacje znajdziesz w wskazówkach dotyczących projektowania.
Umieszczanie obiektu 3D w układzie za pomocą wolumenu
Aby umieścić obiekt 3D w układzie, musisz użyć komponentu podprzestrzeni zwanego objętością. 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, ) } } }
@Composable fun ObjectInAVolume(show3DObject: Boolean) {
Dodatkowe informacje
- Aby dowiedzieć się więcej o wczytywaniu treści 3D w objętowej usłudze, przeczytaj artykuł Dodawanie modeli 3D do aplikacji.
Dodawanie powierzchni na potrzeby treści w postaci obrazu lub filmu
SpatialExternalSurface
to komponent podprzestrzeni, który tworzy i zarządza Surface
, z którego aplikacja może pobierać treści, np. obraz lub film. SpatialExternalSurface
obsługuje treści stereoskopowe i monoskopowe.
Ten przykład pokazuje, jak wczytać filmy stereoskopowe side-by-side za pomocą Media3 Exoplayer i SpatialExternalSurface
:
@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 parametr
StereoMode
naMono
,SideBySide
lubTopBottom
w zależności od tego, jakie treści renderujesz:Mono
: obraz lub kadr wideo składa się z jednego, identycznego obrazu wyświetlanego na obie oczy.SideBySide
: obraz lub kadr wideo zawiera parę obrazów lub klatek wideo ułożonych obok siebie, gdzie obraz lub kadr po lewej stronie przedstawia widok z lewego oka, a obraz lub kadr po prawej stronie przedstawia widok z prawego oka.TopBottom
: obraz lub kadr wideo zawiera parę obrazów lub klatek wideo ułożonych pionowo, gdzie obraz lub kadr na górze przedstawia widok z lewego oka, a obraz lub kadr na dole przedstawia widok z prawego oka.
SpatialExternalSurface
obsługuje tylko prostokątne powierzchnie.- Ten
Surface
nie rejestruje zdarzeń wprowadzania danych. - Zmian
StereoMode
nie można zsynchronizować 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 usunąć, jeśli w układzie są inne panele.
Dodawanie innych komponentów UI w przestrzeni
Komponenty UI przestrzennego można umieścić w dowolnym miejscu w hierarchii UI aplikacji. Elementy te 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ć menu, dialogi i inne komponenty bez konieczności dwukrotnego pisania kodu. Aby lepiej zrozumieć, jak używać tych elementów, zapoznaj się z podanymi niżej przykładami interfejsu przestrzennego.
Komponent interfejsu użytkownika |
Gdy włączona jest lokalizacja przestrzenna |
W środowisku 2D |
---|---|---|
|
Panel przesunie się nieco w głębi, aby wyświetlić okno dialogowe |
Przełącza się na widok 2D |
|
Panel przesunie się nieco w głębi, aby wyświetlić wyskakujące okienko |
Przełącza się na widok 2D |
|
Wartość |
programy bez dźwięku przestrzennego. |
SpatialDialog
Oto przykład okna, które otwiera się po krótkim opóźnieniu. Gdy używasz SpatialDialog
, okno dialogowe pojawia się na tej samej głębokości, co panel przestrzenny, a panel jest przesuwany do tyłu o 125 dp, gdy włączona jest lokalizacja przestrzenna. SpatialDialog
można też używać, gdy przestrzenność nie jest włączona. W takim przypadku SpatialDialog
przechodzi 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
- Oto przykład
SpatialDialog
. Użycie znacznikówSpatialPopup
iSpatialElevation
jest bardzo podobne. Więcej informacji znajdziesz w dokumentacji interfejsu API.
Tworzenie paneli i układów niestandardowych
Aby tworzyć panele niestandardowe, których nie obsługuje Compose for XR, możesz pracować bezpośrednio z instancjami PanelEntity
i grafami sceny za pomocą interfejsów API SceneCore
.
stosowanie orbiterów do osi czasu i innych elementów,
Możesz zaankować orbiter do dowolnego elementu zadeklarowanego w składni. Polega to na zadeklarowaniu orbitera w układzie przestrzennym elementów interfejsu, takich jak SpatialRow
, SpatialColumn
lub SpatialBox
. Orbiter jest przytwierdzony do nadrzędnego elementu, który znajduje się najbliżej miejsca jego zadeklarowania.
Zachowanie orbitera zależy od miejsca jego zadeklarowania:
- W układzie 2D ujętym w element
SpatialPanel
(jak pokazano w poprzednim fragmencie kodu) orbiter jest zakotwiczony w tym elemencie.SpatialPanel
- W
Subspace
orbiter jest zakotwiczony do najbliższej nadrzędnej, czyli do układu przestrzennego, w którym jest zadeklarowany.
Ten przykład pokazuje, jak zakotwiczyć orbiter w wierszu przestrzennym:
Subspace { SpatialRow { Orbiter( position = OrbiterEdge.Top, offset = EdgeOffset.inner(8.dp), shape = SpatialRoundedCornerShape(size = CornerSize(50)) ) { Text( "Hello World!", style = MaterialTheme.typography.h2, 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
- Jeśli deklarujesz orbiter poza układem 2D, orbiter jest przytwierdzony do najbliższego elementu nadrzędnego. W tym przypadku orbiter jest zakotwiczony u góry
SpatialRow
, w którym jest zadeklarowany. - Układy przestrzenne, takie jak
SpatialRow
,SpatialColumn
iSpatialBox
, mają powiązane z sobą elementy bez treści. Dlatego orbiter zadeklarowany w układzie przestrzennym jest do niego przytwierdzony.
Zobacz również
- Dodawanie modeli 3D do aplikacji
- Tworzenie interfejsu użytkownika dla aplikacji opartych na widokach Androida
- Wdrażanie Material Design na potrzeby XR