Понять и внедрить основы

Навигация описывает способ перемещения пользователей по вашему приложению. Пользователи взаимодействуют с элементами пользовательского интерфейса, обычно нажимая или щелкая по ним, а приложение отвечает отображением нового контента. Если пользователь хочет вернуться к предыдущему контенту, он использует жест «назад» или нажимает кнопку «назад».

Моделирование состояния навигации

Удобный способ моделирования такого поведения — стек контента. Когда пользователь переходит к новому контенту, он помещается наверх стека. Когда пользователь возвращается от этого контента, он выталкивается из стека, и отображается предыдущий контент. В терминах навигации этот стек обычно называют стеком возврата, поскольку он представляет контент, к которому пользователь может вернуться .

Кнопка действия программной клавиатуры (значок галочки), обведенная красным.
Рисунок 1. Диаграмма, показывающая, как изменяется стек переходов в зависимости от событий навигации пользователя.

Создайте обратную стопку

В Navigation 3 стек возвратов фактически не содержит контент. Вместо этого он содержит ссылки на контент , известные как ключи . Ключи могут быть любого типа, но обычно представляют собой простые сериализуемые классы данных. Использование ссылок вместо контента имеет следующие преимущества:

  • Навигация осуществляется простым нажатием клавиш на задней панели.
  • Пока ключи сериализуемы, стек возвратов можно сохранить в постоянном хранилище, что позволяет ему пережить изменения конфигурации и смерть процесса. Это важно, поскольку пользователи ожидают, что покинут ваше приложение, вернутся к нему позже и продолжат с того места, где остановились, с тем же отображаемым содержимым. Подробнее см. в разделе Сохраните стек возвратов .

Ключевая концепция Navigation 3 API заключается в том, что вы являетесь владельцем back stack. Библиотека:

  • Ожидает, что ваш back stack будет List<T> поддерживаемым snapshot-state , где T — тип keys back stack . Вы можете использовать Any или предоставить свои собственные, более строго типизированные ключи. Когда вы видите термины «push» или «pop», базовая реализация заключается в добавлении или удалении элементов из конца списка.
  • Наблюдает за вашим стеком переходов и отображает его состояние в пользовательском интерфейсе с помощью NavDisplay .

В следующем примере показано, как создавать ключи и стек переходов, а также изменять стек переходов в ответ на события навигации пользователя:

// 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()
}

Ключи к содержанию

Контент моделируется в Navigation 3 с помощью NavEntry , который является классом, содержащим составную функцию. Он представляет собой пункт назначения — отдельный фрагмент контента, к которому пользователь может перемещаться вперед и назад .

NavEntry также может содержать метаданные — информацию о содержимом. Эти метаданные могут считываться объектами-контейнерами, такими как NavDisplay , чтобы помочь им решить, как отображать содержимое NavEntry . Например, метаданные могут использоваться для переопределения анимаций по умолчанию для определенного NavEntry . metadata NavEntry — это карта String ключей для Any значений, обеспечивающая универсальное хранилище данных.

Чтобы преобразовать key в NavEntry , создайте entryProvider . Это функция, которая принимает key и возвращает NavEntry для этого key . Обычно он определяется как лямбда-параметр при создании NavDisplay .

Существует два способа создания entryProvider : либо путем непосредственного создания лямбда-функции, либо с использованием entryProvider DSL.

Создайте функцию entryProvider напрямую

Обычно вы создаете функцию entryProvider с помощью оператора when , с ветвью для каждого из ваших ключей.

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") }
        }
    }
}

Используйте entryProvider DSL

entryProvider DSL может упростить вашу лямбда-функцию, избежав необходимости проверки каждого из типов ключей и создания NavEntry для каждого из них. Используйте для этого функцию-конструктор entryProvider . Она также включает в себя резервное поведение по умолчанию (выдача ошибки), если ключ не найден.

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

Обратите внимание на следующее в этом фрагменте:

  • entry используется для определения NavEntry с заданным типом и компонуемым содержимым
  • entry принимает параметр metadata для установки NavEntry.metadata

Отобразить стек назад

Back Stack представляет состояние навигации вашего приложения. Всякий раз, когда back stack изменяется, пользовательский интерфейс приложения должен отражать новое состояние back stack. В Navigation 3 NavDisplay наблюдает за вашим back stack и обновляет свой пользовательский интерфейс соответствующим образом. Создайте его со следующими параметрами:

  • Ваш стек возврата - это должен быть тип SnapshotStateList<T> , где T - это тип ключей вашего стека возврата. Это наблюдаемый List , так что он запускает перекомпоновку NavDisplay при его изменении.
  • entryProvider для преобразования ключей в вашем стеке переходов в NavEntry .
  • При желании можно указать лямбду для параметра onBack . Она вызывается, когда пользователь запускает событие back.

В следующем примере показано, как создать 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") }
            }
        }
    )
}

По умолчанию NavDisplay показывает самый верхний NavEntry на заднем стеке в макете одной панели. Следующая запись показывает, как это приложение работает:

Поведение `NavDisplay` по умолчанию с двумя пунктами назначения.
Рисунок 2. Поведение NavDisplay по умолчанию с двумя пунктами назначения.

Собираем все вместе

На следующей диаграмме показано, как данные передаются между различными объектами в Navigation 3:

Визуализация потоков данных между различными объектами в Navigation 3.
Рисунок 3. Диаграмма, показывающая, как данные проходят через различные объекты в Navigation 3.
  1. События навигации инициируют изменения . Ключи добавляются или удаляются из стека возвратов в ответ на взаимодействие с пользователем.

  2. Изменение состояния стека переходов запускает извлечение контента . NavDisplay (компонуемый элемент, который отображает стек переходов) наблюдает за стеком переходов. В своей конфигурации по умолчанию он отображает самую верхнюю запись стека переходов в макете одной панели. Когда верхний ключ в стеке переходов изменяется, NavDisplay использует этот ключ для запроса соответствующего контента у поставщика записей.

  3. Поставщик записи предоставляет контент . Поставщик записи — это функция, которая разрешает ключ в NavEntry . Получив ключ от NavDisplay , поставщик записи предоставляет связанный NavEntry , который содержит как ключ, так и контент.

  4. Отображается содержимое . NavDisplay получает NavEntry и отображает содержимое.