Навигация – это способ перемещения пользователей по приложению. Пользователи взаимодействуют с элементами интерфейса, обычно нажимая на них, а приложение отвечает, отображая новый контент. Если пользователь хочет вернуться к предыдущему контенту, он использует жест назад или нажимает кнопку назад.
Моделирование состояния навигации
Удобный способ смоделировать это поведение – использовать стек контента. Когда пользователь переходит вперед к новому контенту, он помещается в верхнюю часть стека. Когда пользователь нажимает кнопку Назад, контент удаляется из стека и показывается предыдущий. В терминах навигации этот стек обычно называют обратным стеком, поскольку он представляет контент, к которому пользователь может вернуться.
Как создать обратный стек
В 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.
Создать Entry Provider можно двумя способами: напрямую с помощью лямбда-функции или с помощью DSL entryProvider.
Создайте функцию Entry Provider напрямую
Обычно функция 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") } } } }
Используйте entryProvider DSL
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
Показать обратный стек
Обратный стек представляет состояние навигации в приложении. При каждом изменении обратного стека интерфейс приложения должен отражать его новое состояние. В 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 на основе ограничений на уровне сцены и записи.
Подробнее о жизненном цикле в 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и показывает контент.