21、Compose与Navigation:Compose Navigation、NavHost、composable、参数传递、实战:Compose页面导航。
各位同学,今天我们来聊聊 Compose 里的导航。
说实话,Jetpack Navigation 在 View 体系里我就一直在用。到了 Compose 时代,它变得更简洁了。你想想看,以前写一个 Fragment 跳转,要配 XML、写 NavGraph、还要处理 ViewModel 作用域。现在?一个 NavHost 加几个 composable 函数就搞定了。
我个人习惯把导航看作「页面的路由表」。你定义好每个页面的路径,然后通过 navController.navigate() 跳转。就这么简单。
核心组件:NavHost 与 composable
先看最基础的结构。你需要一个 NavHost 作为容器,里面用 composable 注册每个页面。
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home"
) {
composable("home") {
HomeScreen(
onNavigateToDetail = { id ->
navController.navigate("detail/$id")
}
)
}
composable(
route = "detail/{itemId}",
arguments = listOf(
navArgument("itemId") { type = NavType.IntType }
)
) { backStackEntry ->
val itemId = backStackEntry.arguments?.getInt("itemId") ?: 0
DetailScreen(itemId = itemId)
}
}
}
这里有个细节我提一下。startDestination 是应用启动时显示的页面。我在项目中遇到过有人把 startDestination 写成了不存在的路由,结果应用直接白屏。嗯,这个坑踩过一次就记住了。
detail/{itemId}。
参数传递:两种方式
参数传递是导航的核心能力。Compose Navigation 支持两种方式:路径参数和查询参数。
| 类型 | 写法 | 适用场景 |
|---|---|---|
| 路径参数 | detail/{itemId} |
必须传递的参数,如 ID |
| 查询参数 | search?query={keyword} |
可选参数,如搜索关键词 |
路径参数是强制的。如果你定义了一个 {itemId},跳转时必须传值。查询参数是可选的,你可以给默认值。
// 路径参数跳转
navController.navigate("detail/42")
// 查询参数跳转
navController.navigate("search?query=android")
我曾经在项目里犯过一个错:路径参数用了 String 类型,但实际传的是数字。结果页面一直显示异常。后来才发现 NavType 要匹配。你想想看,类型不匹配,解析出来的值就是 null,能不崩吗?
实战:完整的页面导航
我们做一个简单的例子:首页列表点击跳转到详情页,详情页可以返回。
// 首页
@Composable
fun HomeScreen(onNavigateToDetail: (Int) -> Unit) {
Column {
Text("商品列表", style = MaterialTheme.typography.headlineMedium)
LazyColumn {
items(10) { index ->
ListItem(
headlineContent = { Text("商品 $index") },
modifier = Modifier.clickable {
onNavigateToDetail(index)
}
)
}
}
}
}
// 详情页
@Composable
fun DetailScreen(itemId: Int, onBack: () -> Unit) {
Column {
IconButton(onClick = onBack) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "返回")
}
Text("商品详情 ID: $itemId", style = MaterialTheme.typography.headlineMedium)
}
}
导航的返回操作,我习惯用 navController.popBackStack()。但要注意:如果当前页面是起始页,调用 popBackStack() 会直接退出应用。所以最好加个判断。
if (navController.previousBackStackEntry != null) {
navController.popBackStack()
} else {
// 退出应用或显示确认弹窗
}
popBackStack(),结果跳过了目标页面。后来我改用 navController.navigate(route) { popUpTo("home") } 来清理回退栈。
知识体系总览
下面这张图概括了 Compose Navigation 的核心结构。你看,从 NavController 出发,经过 NavHost,路由到各个 composable 页面。参数传递贯穿其中。
总结
Compose Navigation 的核心就三件事:定义路由、传递参数、处理返回。你只要把 NavHost 搭好,剩下的就是拼积木一样组合页面。
我个人建议:路由字符串统一放在一个常量文件里管理。别到处写魔法字符串,后期维护起来真的很痛苦。
好了,这一节的内容就到这里。代码不多,但都是实战中会反复用到的模式。你可以在自己的项目里试试看,有问题随时交流。