首页 / Android 入门教程 / Compose 导航

Android 入门教程

Compose 导航

本教程共 100 篇 · 第 52 篇 · 更新于 2026-07-28 · 约 9 分钟阅读

AndroidAndroid 入门教程Jetpack Compose导航NavHostNavigation Compose底部导航

52. Compose 导航

本节目标:学会用 Navigation Compose 构建多页面应用,掌握 NavHost 定义路由、navigate 跳转、传参、底部导航栏集成。

核心概念

Navigation Compose 有三个核心角色:

  • NavController:导航控制器,管理页面切换和返回栈。
  • NavHost:导航宿主,显示当前页面的容器。
  • 路由(Route):页面的唯一标识,通常用字符串。

打个比方:NavController 是导航员,NavHost 是舞台,路由是剧本里的场景名。导航员按场景名切换舞台上的内容。

添加依赖

dependencies {
    val navVersion = "2.8.5"
    implementation("androidx.navigation:navigation-compose:$navVersion")
}

基本导航

定义路由和 NavHost:

@Composable
fun NavExample() {
    val navController = rememberNavController()

    NavHost(
        navController = navController,
        startDestination = "home"
    ) {
        composable("home") {
            HomeScreen(
                onNavigateToDetail = { navController.navigate("detail") }
            )
        }
        composable("detail") {
            DetailScreen(
                onBack = { navController.popBackStack() }
            )
        }
    }
}
  • rememberNavController() 创建并记住控制器。
  • NavHost 里用 composable("路由名") 定义每个页面。
  • navController.navigate("detail") 跳转到对应路由。
  • navController.popBackStack() 回退一页。
Note
  • 路由是字符串,但要保持简洁唯一。推荐用页面名,如 "home""detail""settings"

导航传参

方式一:URL 参数

NavHost(navController, startDestination = "home") {
    // 声明参数
    composable(
        "detail/{itemId}",
        arguments = listOf(navArgument("itemId") { type = NavType.IntType })
    ) { backStackEntry ->
        val itemId = backStackEntry.arguments?.getInt("itemId") ?: 0
        DetailScreen(itemId = itemId)
    }
}

// 跳转时传参
navController.navigate("detail/42")

方式二:查询参数(可选)

composable(
    "search?query={query}",
    arguments = listOf(navArgument("query") {
        type = NavType.StringType
        defaultValue = ""
        nullable = true
    })
) { entry ->
    val query = entry.arguments?.getString("query") ?: ""
    SearchScreen(query)
}

// 跳转
navController.navigate("search?query=kotlin")
Tip
  • URL 参数(detail/{itemId})适合必传的参数;查询参数(search?query=)适合可选参数,能设默认值。

类型安全路由(推荐)

手动拼字符串容易出错。Navigation 2.8+ 支持用 Kotlin Serialization 做类型安全路由:

// 先加 Kotlin Serialization 插件
@Serializable
object Home

@Serializable
data class Detail(val itemId: Int)

// NavHost 用对象做路由
NavHost(navController, startDestination = Home) {
    composable<Home> {
        HomeScreen(
            onItemClick = { id ->
                navController.navigate(Detail(id))
            }
        )
    }
    composable<Detail> { backStackEntry ->
        val args: Detail = backStackEntry.toRoute()
        DetailScreen(itemId = args.itemId)
    }
}
Note
  • 类型安全路由用数据类替代字符串,编译期就能检查参数类型,不用手动拼 URL。推荐新项目用这种方式。需要项目启用 Kotlin Serialization 插件。

导航事件处理

防止重复跳转

navController.navigate("detail") {
    // 避免快速连点重复入栈
    launchSingleTop = true
}

跳转时清栈

navController.navigate("home") {
    // 清掉到 home 之间的所有页面
    popUpTo("home") { inclusive = false }
    // 或清到登录页并包含它
    popUpTo("login") { inclusive = true }
}

登录流程示例

// 登录成功后跳转到首页,清掉登录页
navController.navigate("home") {
    popUpTo("login") { inclusive = true }
}
Warning
  • 登录成功后一定要 popUpTo("login") { inclusive = true },否则用户按返回键会回到登录页。

底部导航栏

底部导航是移动应用最常见模式。结合 Scaffold 和 NavigationBar:

@Composable
fun MainScreen() {
    val navController = rememberNavController()
    val screens = listOf(
        BottomScreen("home", "首页", Icons.Default.Home),
        BottomScreen("search", "搜索", Icons.Default.Search),
        BottomScreen("profile", "我的", Icons.Default.Person)
    )

    Scaffold(
        bottomBar = {
            val currentRoute = navController.currentBackStackEntryAsState()
                .value?.destination?.route
            NavigationBar {
                screens.forEach { screen ->
                    NavigationBarItem(
                        selected = currentRoute == screen.route,
                        onClick = {
                            navController.navigate(screen.route) {
                                popUpTo(navController.graph.findStartDestination().id) {
                                    saveState = true
                                }
                                launchSingleTop = true
                                restoreState = true
                            }
                        },
                        icon = { Icon(screen.icon, contentDescription = screen.label) },
                        label = { Text(screen.label) }
                    )
                }
            }
        }
    ) { padding ->
        NavHost(
            navController = navController,
            startDestination = "home",
            modifier = Modifier.padding(padding)
        ) {
            composable("home") { HomeScreen() }
            composable("search") { SearchScreen() }
            composable("profile") { ProfileScreen() }
        }
    }
}
Tip
  • 底部导航的 popUpTo + saveState + restoreState 组合是固定写法:切换 Tab 时保留各 Tab 的状态(比如滚动位置),不会每次都重建。findStartDestination() 找到起始页,清到它但不包含它。

嵌套导航图

把相关页面分组:

NavHost(navController, startDestination = "home") {
    composable("home") { HomeScreen() }

    // 嵌套图
    navigation(startDestination = "list", route = "feature") {
        composable("list") { ListScreen() }
        composable("detail/{id}") { entry ->
            DetailScreen(entry.arguments?.getString("id"))
        }
    }
}

// 跳转到嵌套图的起始页
navController.navigate("feature")

监听导航结果

页面间传回结果:

// 详情页设置结果
navController.previousBackStackEntry
    ?.savedStateHandle
    ?.set("result", "选中了项目3")
navController.popBackStack()

// 列表页监听结果
navController.currentBackStackEntry
    ?.savedStateHandle
    ?.getStateFlow<String>("result", "")
    ?.collectAsStateWithLifecycle()

小结

Navigation Compose 用 NavHost 定义路由、navigate 跳转、popBackStack 回退。推荐用类型安全路由替代字符串拼接。底部导航用 Scaffold + NavigationBar + saveState/restoreState 组合。登录成功记得清栈。下一节学习手势处理。