上篇文章介绍了 Navigation 的集成与基本使用。可以看出来Navigation的引入使我们对我们项目中的fragment的管理更加的井井有条,提高了开发效率。而本文则着重讲一下机遇Compose的Navigation的使用。
Compose下的页面跳转
Compose的显示是需要载体的,传统的Activity也好,Fragment也好,如果基于传统的页面形式来做Compose下的页面跳转的话,最终也还是回到了传统的Activity跳转以及Fragment的管理上来。那么有没有一种针对Compose的量身定做的而不依赖传统Activity以及Fragment的页面管理方式呢。答案是有,一直都有。前有Google推出的Navigation,后有大佬制作的号称
“Easier to complete Jetpack Compose navigation”
的 compose-navigator。本质上都是为了解决 Compose 的页面管理问题,本期文章我们来看一下Google推出的navigation-compose。
集成
dependencies {
implementation "androidx.navigation:navigation-compose:2.4.0-rc01"
}
组成
上篇文章 基于 Fragment的Navigation是由三部分组成,分别是导航图,NavHost,NavController,而基于Compose的Navigation是由NavHost,NavController组成,相比前者少了导航图的资源构建。
使用
NavController
NavController是Navigation的核心api,他维护了Navigation内部关于页面的堆栈以及状态信息,甚至,前文讲的导航图其实也是在NavController中所维护。在Compose中需要通过以下方式获取一个有状态的实例:
val navController = rememberNavController()
NavHost
在上篇文章中讲到了NavHostFragment,NavHostFragment其实是作为Navigation中fragment的显示载体来出现的。而在compose-navigation的体系中,因为没了Fragment的出现,因此相应的,NavHostFragment也由更加抽象的NavHost来实现。其实就是个容器,内部通过持有NavController的引用,在“页面切换”时,渲染当前 Compose UI。那么NavHost的初始化参数我可大致猜出一些端倪,肯定会有:
NavController刚才讲到在compose-navigation体系中导航图是不需要单独在资源文件中定义的,是由NavHost来维护的,因此参考上篇文章中导航图所包含的关键内容来看,NavHost也应该包含:startDestination路由起点NavGraph导航图详细
那么一个简单的实践如下:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "A") {
composable("A") {
ScreenA()
}
composable("B") {
ScreenB()
}
composable("C") {
ScreenC()
}
}
}
}
}
跳转
在NavController小节中,我们讲到了初始化,对于这一点,官方的描述如下:
您应该在可组合项层次结构中的适当位置创建
NavController,使所有需要引用它的可组合项都可以访问它
其本意是指,身处于NavHost中的Compose界面如果要使用Navigation跳转的话,需要使用这个NavHost自身绑定那一个NavController,NavController与NavHost可以有多个,但是不同的NavHost之间的NavController是不能混用的,概括起来可以这么理解 "每一个NavHost,对应一个NavController",栗子🌰(来源自亲爱的好工友 hanbing 好哥哥):
那么如何进行跳转?
先看下NavHost初始化源码:
@Composable
public fun NavHost(
navController: NavHostController,
startDestination: String,
modifier: Modifier = Modifier,
route: String? = null,
builder: NavGraphBuilder.() -> Unit
) {
NavHost(
navController,
remember(route, startDestination, builder) {
navController.createGraph(startDestination, route, builder)
},
modifier
)
}
看下关键的参数
navControllerstartDestinationroutebuildernavController上文已经提过,用来绑定NavController。
startDestination的作用与上篇文章导航图中的
startDestination如出一辙,都是定义了初始界面。
route的作用我的理解是用于不同的NavHost之间的跳转使用的。
最重要的,builder。builder的类型是NavGraphBuilder.() -> Unit,那么
回到NavHost的初始化这里来
NavHost(navController = navController, startDestination = "A") {
composable("A") {
ScreenA("A")
}
composable("B") {
ScreenB("B")
}
composable("C") {
ScreenC("C")
}
}
显而易见lambda内部即是对导航图的定义,包含了当前NavHost的所有页面集合,并且定义了页面属性destination,看一下NavGraphBuilder.composable()方法本身:
public fun NavGraphBuilder.composable(
route: String,
arguments: List<NamedNavArgument> = emptyList(),
deepLinks: List<NavDeepLink> = emptyList(),
content: @Composable (NavBackStackEntry) -> Unit
) {
addDestination(
ComposeNavigator.Destination(provider[ComposeNavigator::class], content).apply {
this.route = route
arguments.forEach { (argumentName, argument) ->
addArgument(argumentName, argument)
}
deepLinks.forEach { deepLink ->
addDeepLink(deepLink)
}
}
)
}
可以看到composable函数本身是NavGraphBuilder的扩展函数,本质是调用了NavGraphBulder的addDestination函数。可以通过这个函数来传递子级NavHost:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val parentNavController = rememberNavController()
val navController1 = rememberNavController()
NavHost(navController = navController1, startDestination = "Stack1A", route = "Stack1") {
composable("Stack1A") {
Greeting(parentNavController, navController1, "A", "Stack1")
}
composable("Stack1B") {
Greeting(parentNavController, navController1, "B", "Stack1")
}
composable("Stack1C") {
Greeting(parentNavController, navController1, "C", "Stack1")
}
}
val navController2 = rememberNavController()
NavHost(navController = navController2, startDestination = "Stack2A", route = "Stack2") {
composable("Stack2A") {
Greeting(parentNavController, navController2, "A", "Stack2")
}
composable("Stack2B") {
Greeting(parentNavController, navController2, "B", "Stack2")
}
composable("Stack2C") {
Greeting(parentNavController, navController2, "C", "Stack2")
}
}
NavHost(navController = parentNavController, startDestination = "Stack1") {
addDestination(navController1.graph)
addDestination(navController2.graph)
}
}
}
}
具体的跳转方式跟前文一致:
navController.navigate("destination")
destination既可以传递定义单个NavHost节点是传入的composable名也可以传递deepLink等链接
参数传递
本质上是在routes参数中添加占位符,
NavHost(startDestination = "profile/{userId}") {
...
composable(
"profile/{userId}",
arguments = listOf(navArgument("userId") { type = NavType.StringType })
) {...}
}
跳转时,调用navController.navigate()传递具体的参数值比如profile/123
总结
compose-navigation的引入让compose的使用更加灵活,不需要依赖的fragment以及Activity即可自行完成页面的跳转与管理维护。而且新版本的compose-navigation已经对 Parcelable类型的argument进行了支持(通过currentBackStackEntry获取参数)
// In the source screen...
navController.currentBackStackEntry?.arguments =
Bundle().apply {
putParcelable("argument", argument)
}
navController.navigate("newPage")