Навигация – это способ, которым пользователи перемещаются по приложению. Они взаимодействуют с элементами интерфейса, обычно нажимая на них, а приложение в ответ показывает новый контент. Если пользователь хочет вернуться к предыдущему контенту, он может использовать жест "Назад" или нажать кнопку "Назад".
Моделирование состояния навигации
Удобный способ смоделировать такое поведение – использовать стопку контента. Когда пользователь переходит к новому контенту, он добавляется в верхнюю часть стека. Когда пользователь возвращается к предыдущему контенту, он извлекается из стека и показывается. В контексте навигации этот стек обычно называют стеком возврата, поскольку он представляет контент, к которому пользователь может вернуться.
Как создать стек возврата
В Navigation 3 стек возврата не содержит контент. Вместо этого в нем содержатся ссылки на контент, которые называются ключами. Ключи могут быть любого типа, но обычно это простые сериализуемые классы данных. Использование ссылок вместо контента имеет следующие преимущества:
- Перемещаться по нему очень просто.
- Если ключи можно сериализовать, стек возврата можно сохранить в постоянном хранилище, чтобы он не терялся при изменении конфигурации и завершении процесса. Это важно, поскольку пользователи ожидают, что смогут вернуться в приложение и продолжить с того места, на котором остановились. Подробнее о том, как сохранить обратную навигацию…
Ключевая концепция Navigation 3 API заключается в том, что вы управляете стеком возврата. Библиотека:
- Ожидается, что ваш стек возврата будет представлять собой
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 – класса, содержащего composable-функцию. Она представляет собой целевую страницу – отдельный фрагмент контента, на который пользователь может перейти и с которого может вернуться.
NavEntry также может содержать метаданные – информацию о контенте. Эти метаданные могут считываться объектами-контейнерами, например NavDisplay, чтобы определить, как отображать контент NavEntry. Например, метаданные можно использовать, чтобы переопределить анимацию по умолчанию для определенного NavEntry. NavEntry
metadata – это карта, в которой ключи String сопоставлены со значениями Any. Она позволяет хранить данные в разных форматах.
Чтобы преобразовать key в NavEntry, создайте поставщика записей. Это функция, которая принимает 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
Язык entryProvider позволяет упростить функцию lambda, поскольку вам не нужно проверять каждый тип ключа и создавать для него 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.
Как посмотреть историю переходов
Стек возврата представляет состояние навигации приложения. При каждом изменении стека возврата интерфейс приложения должен отражать новое состояние стека. В 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 с двумя пунктами назначения.Жизненный цикл каталога назначения
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:
События навигации инициируют изменения. Ключи добавляются в стек или удаляются из него в ответ на действия пользователя.
Изменение состояния стека возврата вызывает получение контента.
NavDisplay(composable-функция, которая отрисовывает обратный стек) отслеживает обратный стек. По умолчанию он показывает верхнюю запись стека возврата в макете с одной панелью. Когда верхний ключ в стеке возврата меняется,NavDisplayиспользует его, чтобы запросить соответствующий контент у поставщика записей.Поставщик записи предоставляет контент. Поставщик записей – это функция, которая преобразует ключ в
NavEntry. Получив ключ отNavDisplay, поставщик записи предоставляет связанный с ним объектNavEntry, который содержит ключ и контент.Контент показывается.
NavDisplayполучаетNavEntryи показывает контент.