Навигация

Введение в навигацию

Последнее обновление: 17.04.2024

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

Каждый экран обычно представляет собой отдельный компонент или объект Activity. Подобные экраны Google еще называет термином destination, то пункт назначения, к которому может перейти пользователь. Каждое приложение имеет главный экран (home screen), который появляется после запуска приложения и после появления любого экрана-заставки. На этом главном экране пользователь обычно выполняет задачи, которые приводят к появлению других экранов или пунктов назначения. Эти экраны обычно принимают форму других компонентов проекта. Например, приложение для обмена сообщениями может иметь главный экран со списком текущих сообщений, с которого пользователь может перейти на другой экран для доступа к списку контактов или экрану настроек. Экран списка контактов, в свою очередь, может позволить пользователю переходить к другим экранам, где можно добавлять новых пользователей или обновлять существующие контакты.

Для отслеживания переходов по различным экранам Android использует стек навигации. При первом запуске приложения начальный экран помещается в стек. Когда пользователь переходит к другому экрану, то этот экран помещается на верхушку стека. Когда пользователь переходит к другим экранам, они также помещаются в стек. Когда пользователь перемещается обратно по экранам с помощью системной кнопки "Назад", каждый компонент извлекается из стека до тех пор, пока главный экран не окажется единственным элементом в стеке. Извлекать элементы из стеа можно также программно.

Подключение функционала навигации в проект

По умолчанию проект не содержит функционала навигации, и нам надо добавить все необходимые зависимости. Для этого изменим файл libs.version.toml - в секцию [versions] версию подключаемой зависимости, а в секцию [libraries] имя подключемой библиотеки:

[versions]
navigationCompose = "2.7.7"
...................................


[libraries]
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }

Затем в файл build.gradle.kts (Module :app) в секцию dependencies добавим следующую директиву:

dependencies {
    implementation(libs.androidx.navigation.compose)
...................................

Для синхронизации и обновления проекта нажмем на кнопку Sync Now

Создание хоста навигации

Для управления стеком навигации и самой навигацией применяется контроллер навигации, представленным классом NavHostController. И первым шагом при добавлении навигации в проект приложения является создание экземпляра NavHostController:

val navController = rememberNavController()

Для создания объекта NavHostController применяется функция rememberNavController(). Данная функция позволяет сохранить целостность стека навигации в процессе рекомпозиции компонентов пользовательского интерфейса.

После создания контроллера навигации его необходимо назначить хосту навигации - объекту NavHost. Хост навигации (NavHost) — это специальный компонент, который добавляется в макет пользовательского интерфейса действия и служит заполнителем для страниц, по которым будет перемещаться пользователь.

При вызове в NavHost передается объект NavHostController, компонент, который будет служить начальным экраном/начальным пунктом назначения, и граф навигации (navigation graph):

val navController = rememberNavController()
NavHost(navController = navController, startDestination = начальный_маршрут) {

    // Пункты назначения навигационного графа
}

Для передачи графа навигации применяется третий параметр - концевая лямбда. Граф навигации состоит из всех компонентов, которые должны быть доступны в качестве пунктов назначения навигации в контексте контроллера навигации. Пункты назначения добавляются в граф навигации путем вызова функции composable(), в который передается маршрут (route) и пункт назначения. Маршрут представляет обычную строку, которая однозначно идентифицирует пункт назначения в контексте текущего контроллера навигации. Пункт назначения — это компонент, который будет вызываться при выполнении навигации. Например:

NavHost(navController = navController, startDestination = "home") {

    composable("home") {
        Home()
    }
 

    composable("contact") {
        Contacts()
    }

    composable("about") {
        About()
    }
}

Здесь NavHost включает граф навигации, состоящий из трех пунктов назначения, где маршрут "home" настроен как начальный пункт назначения (начальный экран).

В качестве альтернативы жесткому кодированию строк маршрута в вызовах функции composable() можно определять маршруты в закрытом классе:

sealed class Routes(val route: String) {

    object Home : Routes("home")
    object Contacts : Routes("contact")
    object About : Routes("about")
}

NavHost(navController = navController, startDestination = Routes.Home.route) {

    composable(Routes.Home.route) { Home() }
    composable(Routes.Contacts.route) { Contacts()  }
    composable(Routes.About.route) { About() }
}

Использование закрытого класса позволяет определить единую точку для управления маршрутами.

Переход к точкам назначения

Для перехода между точками назначения/экранами применяется метод navigation() контроллера навигации. Данный метод определяет маршрут для пункта назначения. Например, в следующем коде по нажатию на кнопку происходит перехода к экрану "Contacts":

Button(onClick = {

    navController.navigate(Routes.Contacts.route)
}) {

    Text(text = "Contacts")
}

Метод navigation() также с помощью концевой лямбды позволяет задать параметры навигации с помощью различных функций. Так, функция popUpTo() позволяет очистить стек навигации вплоть до определенного элемента. Это упрощает навигацию, когда пользователь хочет вернуться не просто назад к экрану, который хранится на верхушке стека навигации, а в какое-то определенное место, например, на начальный экран. В этом случае мы могли бы сделать следующим образом:

Button(onClick = {

    navController.navigate(Routes.Contacts.route) {

        popUpTo(Routes.Home.route)
    }

}) {

    Text(text = "To Contact Page")
}

Теперь, когда пользователь нажимает кнопку "To Contact Page", в стек очищается вплоть до главного экрана Home. И после посещения экрана Contacts при возвращении назад пользователь перейдет к экрану Home.

Для определения начального пункта назначения также можно использовать метод navController.graph.findStartDestination(), а очистка стека вплоть до этого пункта будет представлять следующий вызов popUpTo:

popUpTo(navController.graph.findStartDestination().id)

Функция popUpTo() также может принимать дополнительные параметры. Например, параметр inclusive указывает, надо ли извлечь из стека навигации также и тот пункт назначения, который передан в функцию. Так, значение inclusive = true в следующем коде также извлекает из стека навигации пункт назначения Home перед выполнением навигации:

Button(onClick = {

    navController.navigate(Routes.Contacts.route) {

        popUpTo(Routes.Home.route) {

            inclusive = true
        }
    }
}) {

    Text(text = "To Contact Page")
}

По умолчанию попытка перехода от текущего пункта назначения к самому себе помещает в стек навигации дополнительный экземпляр пункта назначения. В большинстве ситуаций такое поведение вряд ли будет желательным. Чтобы этого предотвратить, для параметра launchSingleTop устанавливается значение true:

Button(onClick = {

    navController.navigate(Routes.Contacts.route) {

        launchSingleTop = true
    }
}) {

    Text(text = "To Contact Page")
}

Если для параметров saveState и restoreState установлено значение true, они будут автоматически сохранять и восстанавливать состояние записей стека, когда пользователь повторно выбирает пункт назначения, который был выбран ранее.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850