Poznawanie i wdrażanie podstaw

Nawigacja to sposób, w jaki użytkownicy poruszają się po Twojej aplikacji. Wchodzą oni w interakcje z elementami interfejsu, zwykle klikając je lub naciskając, a aplikacja reaguje, wyświetlając nowe treści. Jeśli użytkownik chce wrócić do poprzednich treści, może użyć gestu cofania lub nacisnąć przycisk Wstecz.

Modelowanie stanu nawigacji

Wygodnym sposobem modelowania tego zachowania jest użycie stosu treści. Gdy użytkownik przechodzi dalej do nowych treści, są one umieszczane na wierzchu stosu. Gdy wraca z tych treści, są one zdejmowane ze stosu i wyświetlane są poprzednie treści. W terminologii nawigacji ten stos jest zwykle nazywany stosem wstecz , ponieważ reprezentuje treści, do których użytkownik może wrócić.

Przycisk polecenia klawiatury ekranowej (ikona znacznika wyboru) w czerwonym kółku.
Rysunek 1. Schemat pokazujący, jak stos wstecz zmienia się w zależności od zdarzeń nawigacji użytkownika.

Tworzenie stosu wstecz

W Navigation 3 stos wstecz nie zawiera treści. Zawiera on odniesienia do treści, zwane kluczami. Klucze mogą być dowolnego typu, ale zwykle są to proste, serializowalne klasy danych. Używanie odniesień zamiast treści ma te zalety:

  • Nawigacja jest prosta, ponieważ klucze są umieszczane na stosie wstecz.
  • Dopóki klucze są serializowalne, stos wstecz można zapisać w pamięci trwałej, co pozwala mu przetrwać zmiany konfiguracji i zamknięcie procesu. Jest to ważne, ponieważ użytkownicy oczekują, że będą mogli opuścić aplikację, wrócić do niej później i kontynuować korzystanie z tych samych treści. Więcej informacji znajdziesz w artykule Zapisywanie stosu wstecz.

Kluczową koncepcją w interfejsie Navigation 3 API jest to, że jesteś właścicielem stosu wstecz. Biblioteka:

  • oczekuje, że stos wstecz będzie obsługującym stan migawki List<T>, gdzie T jest typem stosu wstecz keys. Możesz użyć typu Any lub podać własne, bardziej ściśle określone klucze. Gdy widzisz terminy „push” lub „pop”, podstawowa implementacja polega na dodawaniu lub usuwaniu elementów z końca listy.
  • Obserwuje stos wstecz i odzwierciedla jego stan w interfejsie za pomocą elementu NavDisplay.

W tym przykładzie pokazujemy, jak utworzyć klucze i stos wstecz oraz jak modyfikować stos wstecz w odpowiedzi na zdarzenia nawigacji użytkownika:

// Define keys that will identify content
data object ProductList
data class ProductDetail(val id: String)

@Composable
fun MyApp() {

    // Create a back stack, specifying the key the app should start with
    val backStack = remember { mutableStateListOf<Any>(ProductList) }

    // Supply your back stack to a NavDisplay so it can reflect changes in the UI
    // ...more on this below...

    // Push a key onto the back stack (navigate forward), the navigation library will reflect the change in state
    backStack.add(ProductDetail(id = "ABC"))

    // Pop a key off the back stack (navigate back), the navigation library will reflect the change in state
    backStack.removeLastOrNull()
}

Rozwiązywanie kluczy do treści

Treści są modelowane w Navigation 3 za pomocą NavEntry, która jest klasą zawierającą funkcję typu „composable”. Reprezentuje ona miejsce docelowe – pojedynczą treść , do której użytkownik może przejść dalej i wrócić.

Klasa NavEntry może też zawierać metadane – informacje o treści. Te metadane mogą być odczytywane przez obiekty kontenera, takie jak NavDisplay, aby pomóc im w podjęciu decyzji, jak wyświetlić zawartość klasy NavEntry. Metadane mogą na przykład służyć do zastępowania domyślnych animacji dla określonej klasy NavEntry. metadata klasy NavEntry to mapa kluczy String do wartości Any, która zapewnia wszechstronne przechowywanie danych.

Aby przekonwertować key na NavEntry, utwórz dostawcę wpisów. Jest to funkcja, która przyjmuje key i zwraca NavEntry dla tego key. Zwykle jest ona definiowana jako parametr lambda podczas tworzenia NavDisplay.

Istnieją 2 sposoby tworzenia dostawcy wpisów: bezpośrednie utworzenie funkcji lambda lub użycie entryProvider DSL.

Bezpośrednie tworzenie funkcji dostawcy wpisów

Funkcję dostawcy wpisów zwykle tworzy się za pomocą instrukcji when z gałęzią dla każdego klucza.

entryProvider = { key ->
    when (key) {
        is ProductList -> NavEntry(key) { Text("Product List") }
        is ProductDetail -> NavEntry(
            key,
            metadata = mapOf("extraDataKey" to "extraDataValue")
        ) { Text("Product ${key.id} ") }

        else -> {
            NavEntry(Unit) { Text(text = "Invalid Key: $it") }
        }
    }
}

Używanie DSL entryProvider

DSL entryProvider może uprościć funkcję lambda, ponieważ nie trzeba testować każdego typu klucza ani tworzyć NavEntry dla każdego z nich. Użyj do tego funkcji konstruktora entryProvider. Zawiera ona też domyślne zachowanie rezerwowe (zgłaszanie błędu), jeśli klucz nie zostanie znaleziony.

entryProvider = entryProvider {
    entry<ProductList> { Text("Product List") }
    entry<ProductDetail>(
        metadata = mapOf("extraDataKey" to "extraDataValue")
    ) { key -> Text("Product ${key.id} ") }
}

Zwróć uwagę na te elementy fragmentu kodu:

  • entry służy do definiowania NavEntry o danym typie i zawartości kompozycyjnej.
  • entry akceptuje parametr metadata, aby ustawić NavEntry.metadata.

Wyświetlanie stosu wstecz

Stos wstecz reprezentuje stan nawigacji aplikacji. Za każdym razem, gdy stos wstecz się zmienia, interfejs aplikacji powinien odzwierciedlać nowy stan stosu wstecz. W Navigation 3 element NavDisplay obserwuje stos wstecz i odpowiednio aktualizuje interfejs. Skonstruuj go za pomocą tych parametrów:

  • Stos wstecz – powinien być typu SnapshotStateList<T>, gdzie T jest typem kluczy stosu wstecz. Jest to obserwowalna List, która powoduje rekompozycję NavDisplay po zmianie.
  • entryProvider do konwertowania kluczy w stosie wstecz na obiekty NavEntry.
  • Opcjonalnie możesz podać lambdę w parametrze onBack. Jest ona wywoływana, gdy użytkownik wywoła zdarzenie cofania.

W tym przykładzie pokazujemy, jak utworzyć NavDisplay.

data object Home
data class Product(val id: String)

@Composable
fun NavExample() {

    val backStack = remember { mutableStateListOf<Any>(Home) }

    NavDisplay(
        backStack = backStack,
        onBack = { backStack.removeLastOrNull() },
        entryProvider = { key ->
            when (key) {
                is Home -> NavEntry(key) {
                    ContentGreen("Welcome to Nav3") {
                        Button(onClick = {
                            backStack.add(Product("123"))
                        }) {
                            Text("Click to navigate")
                        }
                    }
                }

                is Product -> NavEntry(key) {
                    ContentBlue("Product ${key.id} ")
                }

                else -> NavEntry(Unit) { Text("Unknown route") }
            }
        }
    )
}

Domyślnie NavDisplay wyświetla najwyższy element NavEntry na stosie wstecz w układzie z jednym panelem. Na tym nagraniu widać działanie tej aplikacji:

Domyślne działanie elementu `NavDisplay` z 2 miejscami docelowymi.
Rysunek 2. NavDisplay domyślne działanie z dwoma miejscami docelowymi.

Cykl życia miejsca docelowego

NavDisplay używa niestandardowych elementów LifecycleOwners, aby ograniczyć stan cyklu życia NavEntry na podstawie ograniczeń na poziomie sceny i ograniczeń na poziomie wpisu.

Więcej informacji o cyklach życia w Compose znajdziesz w artykule Cykl życia w Jetpack Compose.

Ograniczenia cyklu życia na poziomie sceny

NavDisplay zarządza cyklem życia aktywnych Scenes. Ograniczenia na poziomie sceny są określane w ten sposób:

W przypadku scen bez nakładek:

  • RESUMED: dozwolone tylko wtedy, gdy przejście sceny zostało zakończone i nie ma aktywnych scen nakładkowych wyświetlanych na jej wierzchu.
  • STARTED: ograniczone do STARTED podczas przejść sceny, np. podczas przechodzenia do przodu lub do tyłu albo gdy jest ona zasłonięta przez nakładkę.

W przypadku scen nakładkowych, takich jak okna dialogowe lub arkusze dolne:

  • RESUMED: dozwolone tylko w przypadku najwyższej, obecnie aktywnej sceny nakładkowej.
  • STARTED: ograniczone do STARTED w przypadku wszystkich scen nakładkowych, które są zasłonięte przez nowszą nakładkę.

Stan cyklu życia na poziomie wpisu

Biblioteka zarządza maksymalnym stanem cyklu życia każdego NavEntry na podstawie jego obecności w stosie wstecz:

  • RESUMED: jeśli wpis znajduje się w bieżącym stosie wstecz, jego cykl życia może osiągnąć stan RESUMED (z zastrzeżeniem ograniczenia na poziomie sceny).
  • CREATED: jeśli wpis nie znajduje się już w stosie wstecz, np. gdy został zdjęty ze stosu, ale nadal jest renderowany na ekranie podczas animacji, biblioteka ściśle ogranicza jego cykl życia do stanu CREATED. Ten limit zapewnia, że wpisy w tle lub wychodzące przestaną wykonywać aktywne zadania, takie jak zbieranie przepływów lub uruchamianie współprogramów powiązanych ze stanami RESUMED lub STARTED, gdy zakończą przejścia wyjścia.

Jak się łączą

Na przykład ostateczny stan cyklu życia NavEntry jest określany w ten sposób:

Scenariusz Ograniczenie na poziomie sceny Ograniczenie na poziomie wpisu Efektywne ograniczenie
Aktywny wpis, ustabilizowany ekran (bez przejść ani nakładek) RESUMED RESUMED RESUMED
Aktywny wpis, podczas przejścia (przechodzenie do lub z) STARTED RESUMED STARTED
Aktywny wpis, zasłonięty przez nakładkę (np. otwarte okno dialogowe) STARTED RESUMED STARTED
Wpis zdjęty ze stosu, animacja wyjścia STARTED lub RESUMED CREATED CREATED

Łączę wszystko w całość

Ten schemat pokazuje, jak dane przepływają między różnymi obiektami w Navigation 3:

Wizualizacja przepływu danych między różnymi obiektami w Nawigacji 3.
Rysunek 3. Schemat pokazujący, jak dane przepływają przez różne obiekty w Navigation 3.
  1. Zmiany są inicjowane przez zdarzenia nawigacji. Klucze są dodawane do stosu wstecz lub z niego usuwane w odpowiedzi na interakcje użytkownika.

  2. Zmiana stanu stosu wstecz powoduje pobieranie treści. NavDisplay (kompozycja, która renderuje stos wstecz) obserwuje stos wstecz. W konfiguracji domyślnej wyświetla ona najwyższy wpis stosu wstecz w układzie z jednym panelem. Gdy zmieni się najwyższy klucz na stosie wstecz, NavDisplay użyje tego klucza, aby poprosić o odpowiednią treść od dostawcy wpisów.

  3. Dostawca wpisów dostarcza treści. Dostawca wpisów to funkcja, która rozwiązuje klucz do NavEntry. Po otrzymaniu klucza od NavDisplay dostawca wpisów udostępnia powiązany NavEntry, który zawiera zarówno klucz, jak i treść.

  4. Treść jest wyświetlana. NavDisplay otrzymuje NavEntry i wyświetla treść.