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 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ć 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żesz go wywołać w głównej aktywności w taki sam sposób, w jaki używasz setContent(). Subprzestrzeń na poziomie aplikacji jest nieograniczona pod względem wysokości, szerokości i głębi, co zapewnia nieskończoną przestrzeń dla treści przestrzennych.
  • Subspace: ten komponent może być umieszczony w dowolnym miejscu w hierarchii interfejsu aplikacji, co pozwala zachować układy interfejsu 2D i przestrzennego bez utraty kontekstu między plikami. Dzięki temu łatwiej jest udostępniać elementy, takie 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 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 statyczne lub inne treści w panelu przestrzennym.

Przykład panelu UI w przestrzeni

Za pomocą SubspaceModifier możesz zmienić rozmiar, zachowanie i pozycjonowanie 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

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.

Przykład orbitera

Jak widać 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ą.

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

  • 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 tym, jak używać i projektować orbitery, 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, 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 w układzie, 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

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

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 też używać, gdy przestrzenność nie jest włączona. W takim przypadku SpatialDialog przechodzi do swojego odpowiednika 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.

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 fragmentie 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.titleLarge,
                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 deklarowanej przez siebie kolumny SpatialRow.
  • Układy przestrzenne, takie jak SpatialRow, SpatialColumnSpatialBox, mają powiązane z sobą elementy bez treści. Dlatego orbiter zadeklarowany w układzie przestrzennym jest do niego przywiązany.

Zobacz również