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

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

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

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

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

Создать стек возврата

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

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

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

  • Предполагается, что ваш стек возврата будет представлять собой List<T> , хранящийся в состоянии снимка, где T — тип keys вашего стека возврата. Вы можете использовать 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 создайте Entry Provider. Это функция, которая принимает key и возвращает объект NavEntry для этого key . Обычно она определяется как параметр лямбда-функции при создании NavDisplay .

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

Создайте функцию поставщика данных напрямую.

Обычно функцию Entry Provider создают с помощью оператора 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") }
        }
    }
}

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

DSL entryProvider может упростить вашу лямбда-функцию, избавив от необходимости проверять каждый из типов ключей и создавать 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) представляет собой состояние навигации вашего приложения. При каждом изменении состояния стека возврата пользовательский интерфейс приложения должен отражать новое состояние. В Navigation 3 компонент NavDisplay отслеживает состояние стека возврата и соответствующим образом обновляет свой пользовательский интерфейс. Создайте его со следующими параметрами:

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

В следующем примере показано, как создать 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 по умолчанию при наличии двух пунктов назначения.

Жизненный цикл пункта назначения

NavDisplay использует пользовательские LifecycleOwner для ограничения состояния жизненного цикла элемента NavEntry на основе ограничений как уровня сцены , так и уровня элемента.

Для получения дополнительной информации о жизненных циклах в Compose см. раздел «Жизненный цикл в Jetpack Compose» .

Ограничения жизненного цикла на уровне сцены

NavDisplay управляет жизненным циклом активных Scene . Ограничения на уровне сцен определяются следующим образом:

Для сцен без наложения:

  • RESUMED : Разрешено только после завершения перехода между сценами и отсутствия активных наложенных сцен поверх него.
  • STARTED : Время воспроизведения ограничено значением STARTED во время смены сцен, например, при перемотке вперед или назад, или когда сцена закрыта наложением.

Для наложений , таких как диалоги или нижние панели:

  • RESUMED : Разрешено только для самой верхней, активной в данный момент сцены наложения.
  • STARTED : Для всех нижележащих сцен наложения, которые перекрываются более новым наложением, установлено значение STARTED .

Начальное состояние жизненного цикла

Библиотека управляет максимальным состоянием жизненного цикла каждого отдельного элемента NavEntry в зависимости от его присутствия в стеке возврата:

  • RESUMED : Если запись присутствует в текущем стеке возврата, ее жизненный цикл может продолжаться до RESUMED (с учетом ограничения на уровне сцены).
  • CREATED : Если элемент больше не находится в стеке возврата , например, когда он был удален, но все еще отображается на экране во время анимации, библиотека строго ограничивает его жизненный цикл значением CREATED . Это ограничение гарантирует, что фоновые или завершающие выполнение элементы прекращают выполнение активной работы, такой как сбор потоков или запуск сопрограмм, привязанных к состояниям RESUMED или STARTED , пока они завершают свои переходы при выходе.

Как они сочетаются

Например, окончательное состояние жизненного цикла элемента NavEntry определяется следующим образом:

Сценарий Ограничение уровня сцены Начальный уровень Эффективный лимит
Активный вход, фиксированный экран (без переходов и наложений) RESUMED RESUMED RESUMED
Активный вход во время перехода (навигация в или из) STARTED RESUMED STARTED
Активный вход, закрытый наложением (например, открыто диалоговое окно). STARTED RESUMED STARTED
Всплывающее окно, анимированное завершение STARTED или RESUMED CREATED CREATED

Собираем всё воедино

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

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

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

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

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