20、导航与Compose:在Compose中使用Navigation、NavHost与composable()、Compose中的参数传递
各位同学,今天我们来聊聊 Compose 中的导航。说实话,Jetpack Navigation 在传统 View 体系里已经非常成熟了,但到了 Compose 时代,它的用法有了不小的变化。我个人觉得,理解好这套机制,是掌握单 Activity 架构的关键一步。
为什么要在 Compose 里用 Navigation?
你想想看,单 Activity 架构的核心是什么?说白了就是「一个 Activity,多个 Fragment 或 Composable 页面」。在 Compose 中,我们不再需要 FragmentManager 那一套了,取而代之的是 NavHost 和 composable() 函数。
我在项目中遇到过不少团队,刚开始用 Compose 时还是习惯用 Fragment 做页面切换,结果代码越写越重。其实 Compose 的导航设计得非常轻量,你只需要定义好路由,剩下的交给框架就好。
核心概念:
- NavController:导航控制器,负责管理页面栈
- NavHost:导航宿主,用来承载页面内容
- composable():注册页面的函数,类似 Fragment 的导航图
基础用法:三步搞定导航
我们先看一个最简单的例子。假设你有两个页面:首页和详情页。
// 第一步:创建 NavController
val navController = rememberNavController()
// 第二步:定义 NavHost
NavHost(
navController = navController,
startDestination = "home"
) {
// 第三步:注册页面
composable("home") {
HomeScreen(
onNavigateToDetail = { id ->
navController.navigate("detail/$id")
}
)
}
composable(
route = "detail/{itemId}",
arguments = listOf(
navArgument("itemId") { type = NavType.StringType }
)
) { backStackEntry ->
val itemId = backStackEntry.arguments?.getString("itemId")
DetailScreen(itemId = itemId)
}
}
嗯,这里要注意:startDestination 是必须指定的,它决定了应用启动时显示哪个页面。我刚开始用的时候经常忘记写这个,结果页面一片空白,排查了半天才发现是这个问题。
参数传递:不只是字符串
Compose 导航的参数传递比 Fragment 要灵活得多。你可以传递基本类型,也可以传递序列化对象。但说实话,我建议尽量用基本类型,因为复杂对象的传递容易出问题。
| 参数类型 | 路由定义 | 传递方式 |
|---|---|---|
| String | detail/{id} |
navigate("detail/abc") |
| Int | detail/{id} |
navigate("detail/123") |
| Boolean | detail?isPremium={isPremium} |
navigate("detail?isPremium=true") |
| 自定义对象 | 推荐使用 NavType 序列化 | 通过 navArgument 配置 |
我的经验:如果你需要传递复杂对象,可以考虑用 ViewModel 共享数据,或者用 SavedStateHandle。我曾经在项目里直接传了一个大 JSON 字符串,结果导航动画卡顿得不行。后来改成传 ID,在目标页面通过 ViewModel 加载数据,流畅多了。
深层链接与返回栈管理
Compose 的 Navigation 也支持深层链接(Deep Link)。这个功能在推送通知和外部链接跳转时特别有用。
composable(
route = "profile/{userId}",
deepLinks = listOf(
navDeepLink { uriPattern = "myapp://profile/{userId}" }
)
) { backStackEntry ->
val userId = backStackEntry.arguments?.getString("userId")
ProfileScreen(userId = userId)
}
返回栈管理也是重点。默认情况下,navigate() 会把新页面压入栈顶。但有时候我们需要清除返回栈,比如登录成功后跳转到主页。
navController.navigate("home") {
// 清除返回栈中所有页面,除了 "home"
popUpTo("login") { inclusive = true }
// 避免重复创建 "home" 页面
launchSingleTop = true
}
我曾经犯过一个错误:登录成功后没有清除返回栈,用户按返回键又回到了登录页。嗯,这种体验非常糟糕。所以记得用 popUpTo 来管理好返回栈。
Compose 导航的常见坑
我总结几个实际项目中容易踩的坑,给大家提个醒:
- 重复导航:快速点击按钮时,可能会触发多次
navigate()。解决方案是用launchSingleTop = true或者在点击事件里做防抖。 - 参数类型不匹配:路由中的参数类型必须和
navArgument定义的一致。比如你定义了Int类型,传了字符串就会崩溃。 - 状态丢失:Compose 页面重建时,导航状态可能会丢失。建议用
rememberSaveable保存关键状态。
避坑指南:我曾经在项目里用 navigate() 传递了一个很大的列表数据,结果应用直接 OOM 了。后来我改用 Room 数据库 + ID 传递的方式,既安全又高效。记住:导航参数不是用来传大量数据的,它只是路由信息。
知识体系总览
下面这张图是我自己整理的 Compose 导航知识体系,你可以对照着复习:
实战建议
最后,我想给大家一些实战中的建议:
- 统一管理路由:建议把所有路由字符串定义在一个常量类里,避免硬编码。我习惯用
sealed class来封装路由。 - 测试导航逻辑:Compose 的 Navigation 是可以单元测试的。用
NavHostController的测试 API 可以验证页面跳转是否正确。 - 性能优化:如果页面层级很深,考虑用
launchSingleTop避免重复创建。另外,composable()中的内容应该尽量轻量。
好了,关于 Compose 导航的内容就讲到这里。记住,导航是应用的骨架,设计好了,后面的开发会顺畅很多。如果你在实际项目中遇到什么问题,欢迎随时交流。
公众号:蓝海资料掘金营,微信deep3321