Tworzenie interfejsu użytkownika za pomocą Jetpack Compose na potrzeby XR

Dzięki Jetpack Compose for XR możesz deklaratywnie tworzyć interfejs i układ przestrzenny, korzystając z dobrze znanych koncepcji Compose, takich jak wiersze i kolumny. Dzięki temu możesz rozszerzyć istniejące interfejsy Androida na 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ć komponentu Compose i widżetó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 podprzestrzeniprzestrzennych komponentów.

Subspace

Podczas tworzenia aplikacji na Androida XR musisz dodać do niej podprzestrzeń lub układ. Subprzestrzeń 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 domu 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żna go wywołać w MainActivity w taki sam sposób jak setContent. Subprzestrzeń na poziomie aplikacji jest nieograniczona pod względem wysokości, szerokości i głębi, co zapewnia nieograniczone pole do popisu dla treści przestrzennych.
  • Subspace: ten komponent można umieścić 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 przekształcania aplikacji.

Więcej informacji znajdziesz w artykule o dodawaniu podpokoju 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.

  • Uwaga na modyfikatory podprzestrzeni: zwróć uwagę na kolejność interfejsów API SubspaceModifier.
    • Przesunięcie musi wystąpić jako pierwsze w łańcuchu modyfikatorów.
    • Możliwość przenoszenia i zmiany rozmiaru musi być wymieniona na końcu.
    • Obrót musi zostać zastosowany przed skalowaniem.

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 dźwięku przestrzennego

SpatialPanel to komponent podprzestrzeni, który umożliwia wyświetlanie treści aplikacji. Możesz na przykład wyświetlić film, obrazy lub inne treści na panelu przestrzennym.

Przykład panelu UI w przestrzeni

Za pomocą SubspaceModifier możesz zmienić rozmiar, zachowanie i umiejscowienie panelu przestrzennego, jak pokazano w tym przykładzie.

Subspace {
   SpatialPanel(
        SubspaceModifier
           .height(824.dp)
           .width(1400.dp)
           .movable()
           .resizable()
           ) {
          SpatialPanelContent()
      }
}

// 2D content placed within the spatial panel
@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

  • Uwaga dotycząca modyfikatorów podprzestrzeni: zwróć uwagę na kolejność interfejsów API SubspaceModifier.
    • Odsunięcie musi wystąpić jako pierwsze w łańcuchu modyfikatorów.
    • Modyfikatory ruchomości i rozmiaru muszą występować na końcu.
    • Obrót musi zostać zastosowany przed skalowaniem.
  • Ponieważ interfejsy API SpatialPanel są składanymi interfejsami API w subprzestrzeni, musisz je wywoływać w ramach Subspace lub setSubspaceContent. Wywołanie ich poza subprzestrzenią spowoduje wyjątek.
  • Zezwól użytkownikowi na zmianę rozmiaru lub przeniesienie panelu przez dodanie .movable lub .resizable SubspaceModifier.
  • Szczegółowe informacje o rozmiarach i umieszczaniu znajdziesz w wskazówkach dotyczących projektowania paneli przestrzennych. Więcej informacji o implementacji kodu znajdziesz w dokumentacji.

Tworzenie orbitera

Orbiter to komponent UI przestrzennego. Jest ona przeznaczona do dołączania do odpowiedniego panelu przestrzennego i zawiera elementy nawigacji oraz działania kontekstowe powiązane z tym panelem. Jeśli na przykład utworzysz panel przestrzenny do wyświetlania treści wideo, możesz dodać elementy sterujące odtwarzaniem filmu w orbicie.

Przykład orbitera

Jak pokazano w następnym przykładzie, wywołaj orbiter w komponencie SpatialPanel, aby owinąć elementy sterowania użytkownika, takie jak nawigacja. W ten sposób wyodrębniasz je z układu 2D i dołączasz do panelu przestrzennego zgodnie z konfiguracją.

setContent {
    Subspace {
        SpatialPanel(
            SubspaceModifier
                .height(824.dp)
                .width(1400.dp)
                .movable()
                .resizable()
        ) {
            SpatialPanelContent()
            OrbiterExample()
        }
    }
}

//2D content inside Orbiter
@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

  • Uwaga na temat modyfikatorów przestrzeni podrzędnej: zwróć uwagę na kolejność interfejsów API SubspaceModifier.
    • Przesunięcie musi wystąpić jako pierwsze w łańcuchu modyfikatorów.
    • Możliwość przenoszenia i zmiany rozmiaru musi być wymieniona na końcu.
    • Obrót musi zostać zastosowany przed skalowaniem.
  • Orbitery to komponenty UI przestrzennego, więc kod można ponownie wykorzystać w układzie 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 ramach układu przestrzennego za pomocą elementów SpatialRow, SpatialColumn, SpatialBoxSpatialLayoutSpacer.

Przykład wielu paneli przestrzennych w układzie przestrzennym

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

Umieszczanie obiektu 3D w układzie za pomocą wolumenu

Aby umieścić obiekt 3D na stronie, musisz użyć komponentu podprzestrzeni zwanego objętością. Oto przykład, jak to zrobić.

Przykład obiektu 3D w układzie

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) {
    val xrCoreSession = checkNotNull(LocalSession.current)
    val scope = rememberCoroutineScope()
    if (show3DObject) {
        Subspace {
            Volume(
                modifier = SubspaceModifier
                    .offset(volumeXOffset, volumeYOffset, volumeZOffset) //
Relative position
                    .scale(1.2f) // Scale to 120% of the size

            ) { parent ->
                scope.launch {
                   // Load your 3D Object here
                }
            }
        }
    }
}

Najważniejsze informacje o kodzie

  • Uwaga na temat modyfikatorów podprzestrzeni: zwróć uwagę na kolejność interfejsów API SubspaceModifier.
    • Przesunięcie musi wystąpić jako pierwsze w łańcuchu modyfikatorów.
    • Możliwość przenoszenia i zmiany rozmiaru musi być wymieniona na końcu.
    • Obrót musi zostać zastosowany przed skalowaniem.
  • Aby dowiedzieć się więcej o wczytywaniu treści 3D w tomach, przeczytaj artykuł Dodawanie treści 3D.

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

SpatialDialog

Panel przesunie się nieco w głębi, aby wyświetlić okno dialogowe na pierwszym planie

Przełącza się na widok 2D Dialog.

SpatialPopUp

Panel przesunie się nieco w głębi, aby wyświetlić wyskakujące okienko

Przełącza się na widok 2D PopUp.

SpatialElevation

SpatialElevationLevel może być ustawiony tak, aby dodawać wysokość.

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 pojawia się na tej samej głębi, co panel przestrzenny, a panel jest przesuwany do tyłu o 125 dp, gdy włączona jest lokalizacja przestrzenna. SpatialDialog można nadal używać, gdy przestrzenność nie jest włączona. W takim przypadku SpatialDialog przełączy się na odpowiednik 2D: Dialog.

@Composable
fun DelayedDialog() {
   var showDialog by remember { mutableStateOf(false) }
   LaunchedEffect(Unit) {
       Handler(Looper.getMainLooper()).postDelayed({
           showDialog = true
       }, 3000)
   }
   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

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 PanelEntities i grafami sceny za pomocą interfejsów API SceneCore.

Zobacz również