前言
Navigation Compose
是 Jetpack Compose 中的一个导航库,它可以帮助你在基于 Compose 的 Android 应用中实现屏幕之间的导航。
这个就类似Vue中的vue-router,可以使用也可以不使用。
以下是使用 Navigation Compose
的详细步骤:
添加依赖
在你的项目的 build.gradle
文件中添加 Navigation Compose
的依赖:1
2
3
4
5dependencies {
implementation("androidx.navigation:navigation-compose:2.7.7")
// 动画扩展
implementation("com.google.accompanist:accompanist-navigation-animation:0.32.0")
}
路由导航
AppNavigation.kt
定义导航图
导航图是一个描述应用中所有可导航目的地的图。
可以使用 NavGraphBuilder
来定义导航图。
基本示例
以下是一个简单的示例:
1 | import androidx.navigation.NavGraphBuilder |
在这个示例中,我们定义了一个名为 root
的导航图,它的起始目的地是 screenLogin
。
导航图中有两个可导航的目的地:screenLogin
和 screenMain
。
使用路由常量
1 | // 定义路由常量(推荐使用密封类) |
创建导航宿主
导航宿主是一个 Composable,它负责显示当前的目的地。你可以使用 NavHost
来创建导航宿主。
基本示例
以下是一个示例:
1 | import androidx.compose.runtime.Composable |
在这个示例中,我们使用 rememberNavController
来创建一个 NavController
,它负责管理导航状态。
然后,我们使用 NavHost
来创建导航宿主,并将 rootNavGraph
作为导航图传递给它。
跳转动画
1 |
|
主Composable使用导航
最后,我们需要在主 Composable 中使用 AppNavigation
:
1 | import androidx.compose.foundation.layout.fillMaxSize |
Activity设置主Composable
这样项目就需要一个Activity了。
在你的 Activity 中,使用 setContent
方法来设置 MyApp
作为内容:
1 | import androidx.appcompat.app.AppCompatActivity |
通过以上步骤,你就可以在你的 Jetpack Compose 应用中使用 Navigation Compose
来实现屏幕之间的导航了。
创建屏幕Composable
现在,我们需要创建每个屏幕的 Composable。
以下是 ScreenLogin
和 ScreenMain
的示例:
ScreenLogin
1 | import androidx.compose.foundation.layout.Arrangement |
ScreenMain
1 | import androidx.compose.foundation.layout.Arrangement |
在 ScreenLogin
中,我们使用 navController.navigate("screenMain")
来导航到 ScreenMain
。
在 ScreenMain
中,我们使用 navController.popBackStack()
来返回到上一个屏幕。
路由操作
跳转
1 | navController.navigate("screenMain") |
替换路由
1 | navController.navigate("screenMain") { |
关键原理说明
popUpTo(currentRoute)
:指定要弹出到哪个路由inclusive = true
:表示将currentRoute
本身也从返回栈中移除- 这样新导航的页面会替代被弹出的页面,用户返回时会直接回到更早的页面
返回
1 | navController.popBackStack() |
返回到指定路由
1 | navController.popBackStack(route = MyScreen.ScreenMain.route, inclusive = false) |
带参数的导航
单个参数
定义带参数的路径
创建一个生成实际路由的方法,替换占位符
1 | sealed class MyScreen { |
传递的参数不能是对象。
定义接收参数
在 NavHost 中定义可组合的屏幕,包括带参数的屏幕
1 | // 详情屏幕(带参数) |
导航到带参数的屏幕
使用 createRoute 方法生成实际的路由字符串。
1 | navController.navigate(MyScreen.Detail.createRoute("123")) |
多个参数
定义带参数的路径
创建一个生成实际路由的方法,替换占位符
1 | sealed class MyScreen { |
传递的参数不能是对象。
定义接收参数
在 NavHost 中定义可组合的屏幕,包括带参数的屏幕
1 | composable( |
屏幕
1 |
|
导航到带参数的屏幕
使用 createRoute 方法生成实际的路由字符串。
1 | navController.navigate(MyScreen.ScreenVideoPlay.createRoute("123",1)) |
起始路由传参
使用navController.navigate(MyScreen.ScreenSubjectQues.createRoute("1956278715385204738"))
才可以。
正确写法
1 |
|
错误写法
在startDestination
中设置,虽然能跳转到指定页面,但是获取不到参数。
1 | navigation( |
页面返回重建
Navigation Compose 在返回时会重新创建上一个页面的 Composable(这是官方设计的默认行为)。
这时候组件内的LaunchedEffect
也会重新执行
1 | LaunchedEffect(Unit) { |
这和我们平时开发逻辑就不太一样。
我们第一想到就是怎样让返回不重建,但是这是错误的。
Navigation Compose 的设计理念就是通过重组更新 UI,强制阻止重建可能会导致状态不一致、内存泄漏等问题。
保留页面实例会增加内存消耗,尤其是在复杂页面或多页面场景下。
可以的方案是
使用
rememberSaveable
或 ViewModel 保存状态(即使页面重建也能恢复)是更合理的选择,既符合 Compose 范式,又能达到类似 “不重建” 的用户体验。
添加初始化的状态
1 | class BaseViewModel : ViewModel() { |
页面中
1 | LaunchedEffect(Unit) { |
这样把状态保留在ViewModel
中,即使重建也不影响页面。
页面返回传值
我用的是NavHost定义导航图,竟然找不到怎么接收返回参数,以下记录一下
跳转前的页面
1 | navController.currentBackStackEntry?.savedStateHandle |
返回页面、返回回调参数
1 | val picPathLiveData = navController.currentBackStackEntry?.savedStateHandle?.getLiveData<String>("picPath") |
注意要在页面销毁的时候移除观察者,否则回调会触发多次。