Зачем нужна навигация и как она устроена в Compose
Навигация — это механизм, который позволяет пользователю перемещаться между разделами приложения. В Compose за это отвечает библиотека navigation-compose, входящая в Jetpack. Она работает на основе графа навигации: вы описываете маршруты, а библиотека сама управляет анимациями, стеком и восстановлением состояния. Для тех, кто только начинает изучать Android Studio Kotlin уроки, навигация станет первым шагом к построению многоэкранных приложений.
Шаг 1: Добавляем зависимость
Откройте файл build.gradle.kts на уровне модуля и добавьте библиотеку навигации. Убедитесь, что версия актуальна — на момент написания урока это 2.8.0.
dependencies {
implementation("androidx.navigation:navigation-compose:2.8.0")
}
После синхронизации Gradle библиотека готова к использованию. Никаких дополнительных настроек не требуется.
Шаг 2: Определяем маршруты
Маршрут — это строковый идентификатор экрана. Хорошей практикой считается выносить их в константы, чтобы избежать опечаток. Создайте объект Routes:
object Routes {
const val HOME = "home"
const val DETAILS = "details"
}
Пока у нас два экрана: главный и экран с деталями. Позже вы сможете добавлять сюда любое количество маршрутов.
Шаг 3: Создаём экраны
Для наглядности сделаем два компонуемых метода. Первый — HomeScreen — будет содержать приветствие и кнопку перехода. Второй — DetailsScreen — просто покажет текст.
@Composable
fun HomeScreen(onNavigate: () -> Unit) {
Column(
modifier = Modifier.fillMaxSize().padding(24.dp),
verticalArrangement = Arrangement.Center
) {
Text("Главный экран", style = MaterialTheme.typography.headlineMedium)
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = onNavigate) {
Text("Перейти к деталям")
}
}
}
@Composable
fun DetailsScreen() {
Column(
modifier = Modifier.fillMaxSize().padding(24.dp),
verticalArrangement = Arrangement.Center
) {
Text("Экран с деталями", style = MaterialTheme.typography.headlineMedium)
}
}
Обратите внимание на параметр onNavigate — через него мы будем запускать переход из главного экрана. Это стандартный подход в Compose: экраны не знают о навигации напрямую, а лишь вызывают переданные колбэки.
Шаг 4: Строим NavHost
Сердце навигации — компонент NavHost. Он принимает контроллер navController и стартовый маршрут, а внутри описываются все экраны с помощью composable(). Обернём это в Scaffold для красивого отступа под верхнюю панель.
val navController = rememberNavController()
Scaffold { paddingValues ->
NavHost(
navController = navController,
startDestination = Routes.HOME,
modifier = Modifier.padding(paddingValues)
) {
composable(Routes.HOME) {
HomeScreen(onNavigate = {
navController.navigate(Routes.DETAILS)
})
}
composable(Routes.DETAILS) {
DetailsScreen()
}
}
}
Теперь при нажатии кнопки на главном экране вызывается navController.navigate(), и Compose отображает DetailsScreen. Кнопка «Назад» на устройстве автоматически возвращает пользователя на HomeScreen — стек работает из коробки.
Шаг 5: Передаём параметры между экранами
Часто нужно передать данные, например, идентификатор выбранного элемента. В маршрут можно встроить аргументы, заключив их в фигурные скобки:
object Routes {
const val HOME = "home"
const val DETAILS = "details/{itemId}"
fun details(itemId: Int) = "details/$itemId"
}
Функция details() генерирует правильный путь с подставленным значением. Теперь обновим навигацию и добавим аргумент:
composable(
route = Routes.DETAILS,
arguments = listOf(navArgument("itemId") { type = NavType.IntType })
) { backStackEntry ->
val itemId = backStackEntry.arguments?.getInt("itemId") ?: 0
DetailsScreen(itemId = itemId)
}
Вызов перехода с параметром выглядит так: navController.navigate(Routes.details(42)). В DetailsScreen мы получаем переданный идентификатор и можем отобразить соответствующие данные.
Шаг 6: Возвращаемся назад программно
Кроме аппаратной кнопки «Назад», иногда нужно вернуться на предыдущий экран из кода. Для этого у navController есть метод popBackStack(). Добавим кнопку «Назад» на экран деталей:
@Composable
fun DetailsScreen(itemId: Int, onBack: () -> Unit) {
Column(
modifier = Modifier.fillMaxSize().padding(24.dp),
verticalArrangement = Arrangement.Center
) {
Text("Детали элемента $itemId", style = MaterialTheme.typography.headlineMedium)
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = onBack) {
Text("Назад")
}
}
}
А в composable() передадим колбэк: DetailsScreen(itemId = itemId, onBack = { navController.popBackStack() }). Теперь пользователь может вернуться и по кнопке, и по системной стрелке назад.
Шаг 7: Добавляем анимации переходов
Простые переходы можно украсить анимациями. Библиотека навигации поддерживает enterTransition и exitTransition. Например, сделаем плавное появление и сдвиг:
composable(
route = Routes.DETAILS,
arguments = listOf(navArgument("itemId") { type = NavType.IntType }),
enterTransition = { fadeIn(animationSpec = tween(300)) + slideInHorizontally(initialOffsetX = { it }) },
exitTransition = { fadeOut(animationSpec = tween(300)) }
) { ... }
Теперь экран деталей плавно выезжает справа и затухает при возврате. Такие мелочи делают интерфейс живым и профессиональным.
Полный код MainActivity
Ниже — итоговый вариант, который объединяет все шаги. Скопируйте его в проект и запустите.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
AppNavigation()
}
}
}
}
object Routes {
const val HOME = "home"
const val DETAILS = "details/{itemId}"
fun details(itemId: Int) = "details/$itemId"
}
@Composable
fun AppNavigation() {
val navController = rememberNavController()
Scaffold { paddingValues ->
NavHost(
navController = navController,
startDestination = Routes.HOME,
modifier = Modifier.padding(paddingValues)
) {
composable(Routes.HOME) {
HomeScreen(onNavigate = { navController.navigate(Routes.details(42)) })
}
composable(
route = Routes.DETAILS,
arguments = listOf(navArgument("itemId") { type = NavType.IntType }),
enterTransition = { fadeIn(tween(300)) + slideInHorizontally { it } },
exitTransition = { fadeOut(tween(300)) }
) { backStackEntry ->
val itemId = backStackEntry.arguments?.getInt("itemId") ?: 0
DetailsScreen(itemId = itemId, onBack = { navController.popBackStack() })
}
}
}
}
@Composable
fun HomeScreen(onNavigate: () -> Unit) {
Column(
modifier = Modifier.fillMaxSize().padding(24.dp),
verticalArrangement = Arrangement.Center
) {
Text("Главный экран", style = MaterialTheme.typography.headlineMedium)
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = onNavigate) {
Text("Перейти к деталям")
}
}
}
@Composable
fun DetailsScreen(itemId: Int, onBack: () -> Unit) {
Column(
modifier = Modifier.fillMaxSize().padding(24.dp),
verticalArrangement = Arrangement.Center
) {
Text("Детали элемента $itemId", style = MaterialTheme.typography.headlineMedium)
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = onBack) {
Text("Назад")
}
}
}
Чему вы научились
В этом уроке из цикла Android Studio Kotlin уроки вы освоили навигацию между экранами на Jetpack Compose: создали NavHost, определили маршруты, настроили переходы с параметрами и добавили анимации. Эти знания — база для построения любого многоэкранного приложения. Теперь вы можете спроектировать структуру экранов и управлять стеком навигации осознанно, а не наугад.