11、导航与路由:Navigation Compose入门、NavHost与NavController、参数传递、深层链接
导航,说白了就是页面之间的跳转逻辑。在传统的Android开发中,我们用Intent、FragmentTransaction来管理页面切换,代码写多了容易乱成一锅粥。Jetpack Compose带来了全新的导航方案——Navigation Compose,它用声明式的方式解决了路由问题。
我个人习惯把导航理解为「一张地图」。你站在哪个页面,想去哪个页面,路上要不要带点参数过去,这些都由Navigation Compose帮你规划好。今天我们就来把这套东西彻底搞明白。
核心要点:Navigation Compose的核心是NavHost + NavController的组合。NavController负责导航逻辑,NavHost负责页面容器的渲染。
11.1 Navigation Compose入门
先说说怎么引入。在build.gradle.kts里加上依赖:
dependencies {
implementation("androidx.navigation:navigation-compose:2.7.7")
}
嗯,版本号建议用最新的稳定版。我上个月刚踩过一个坑——用了2.6.x的某个旧版本,结果在参数传递时遇到了类型转换异常,升级到2.7.7就解决了。
Navigation Compose的核心思想很简单:你定义一组「路由」(route),每个路由对应一个可组合函数。NavHost根据当前路由,渲染对应的页面。
举个例子:
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home"
) {
composable("home") {
HomeScreen(navController)
}
composable("profile") {
ProfileScreen(navController)
}
}
}
这里rememberNavController()创建了一个NavController实例,它贯穿整个导航生命周期。NavHost则像一个容器,根据路由字符串决定显示哪个页面。
小技巧:startDestination是应用启动时默认显示的页面。我建议把首页的路由定义成一个常量,比如const val ROUTE_HOME = "home",避免拼写错误。
11.2 NavHost与NavController
NavController是导航的「大脑」。它维护了一个返回栈(back stack),记录了用户访问过的页面历史。你可以调用navController.navigate("route")跳转到新页面,调用navController.popBackStack()返回上一页。
NavHost则是「显示器」。它监听NavController的状态变化,当路由改变时,自动切换到对应的可组合函数。
这里有个容易混淆的点:NavController本身不负责UI渲染,它只管理状态。真正渲染页面的是NavHost里的composable块。
我曾经在项目里犯过一个低级错误——在多个Composable里各自创建了不同的NavController实例,结果导航完全失效。记住:整个导航图应该共享同一个NavController实例。
看一个更完整的例子:
@Composable
fun MyApp() {
val navController = rememberNavController()
Scaffold(
bottomBar = {
BottomNavigationBar(navController)
}
) { paddingValues ->
NavHost(
navController = navController,
startDestination = "home",
modifier = Modifier.padding(paddingValues)
) {
composable("home") { HomeScreen(navController) }
composable("search") { SearchScreen(navController) }
composable("settings") { SettingsScreen(navController) }
}
}
}
底部导航栏的点击事件里,调用navController.navigate()并配合navController.popUpTo()来管理返回栈,避免页面无限叠加。
注意:使用底部导航时,如果用户已经在「首页」又点击了「首页」按钮,不要重复压栈。建议用navController.navigate("home") { popUpTo("home") { inclusive = true } }来处理。
11.3 参数传递
页面之间传参,这是导航里最常用的功能。Navigation Compose支持两种方式:路径参数和查询参数。
路径参数:参数直接嵌入在路由字符串里。
// 定义路由
composable(
route = "detail/{itemId}",
arguments = listOf(
navArgument("itemId") { type = NavType.StringType }
)
) { backStackEntry ->
val itemId = backStackEntry.arguments?.getString("itemId")
DetailScreen(itemId = itemId)
}
// 跳转
navController.navigate("detail/12345")
查询参数:参数跟在问号后面,类似URL的query string。
// 定义路由
composable(
route = "search?query={query}",
arguments = listOf(
navArgument("query") { type = NavType.StringType; defaultValue = "" }
)
) { backStackEntry ->
val query = backStackEntry.arguments?.getString("query") ?: ""
SearchResultScreen(query = query)
}
// 跳转
navController.navigate("search?query=Kotlin")
我个人更推荐路径参数,因为它在返回栈里更清晰,调试时一眼就能看出当前页面是哪个item。查询参数适合可选参数或搜索场景。
传递复杂对象怎么办?我建议把对象序列化成JSON字符串,作为路径参数传递。或者用共享ViewModel,在页面间共享数据。直接传Parcelable对象虽然可行,但容易导致导航图臃肿。
避坑指南:我曾经在参数里传了一个很大的列表对象,结果导航动画卡顿了好几秒。后来改成传ID,页面内部再根据ID从本地数据库查询数据,流畅多了。
11.4 深层链接
深层链接(Deep Link)允许外部应用或通知直接打开你App的某个特定页面。比如用户点击一个商品链接,直接跳转到商品详情页,而不是先打开首页再跳转。
Navigation Compose对深层链接的支持非常优雅。你只需要在composable块里添加deepLinks参数:
composable(
route = "detail/{itemId}",
deepLinks = listOf(
navDeepLink { uriPattern = "myapp://detail/{itemId}" },
navDeepLink { uriPattern = "https://www.example.com/detail/{itemId}" }
)
) { backStackEntry ->
val itemId = backStackEntry.arguments?.getString("itemId")
DetailScreen(itemId = itemId)
}
这里定义了两个深层链接模式:一个是自定义scheme(myapp://),一个是标准的https链接。当系统收到匹配的Intent时,Navigation Compose会自动解析参数并导航到对应页面。
在AndroidManifest.xml里还需要配置一下:
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="myapp"
android:host="detail" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="www.example.com"
android:pathPrefix="/detail" />
</intent-filter>
</activity>
深层链接在推送通知场景下特别有用。用户收到一条推送,点击后直接跳转到对应的消息详情页,而不是App首页。用户体验会好很多。
注意:深层链接的uriPattern里,路径参数名必须和路由里的参数名一致。比如路由是"detail/{itemId}",深层链接里也要用{itemId},不能写成{id}或{productId}。
11.5 知识体系总览
下面这张图总结了本章的核心知识点:
从图中可以看出,Navigation Compose以NavHost和NavController为骨架,参数传递和深层链接为血肉,共同构成了完整的导航体系。理解了这个结构,你就能灵活应对各种页面跳转需求。
最后说一句:导航是App的骨架,骨架稳了,功能才能往上堆。花点时间把导航设计好,后面开发会顺畅很多。
公众号:蓝海资料掘金营,微信deep3321