持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第23天,点击查看活动详情
概述
在之前我们已经学习了使用导航组件在Fragment
之间进行导航的操作,对于单Activity
多Fragment
的应用来说,使用导航组件能够极大地方便我们的开发,我们可以通过导航图直观地看到页面的流转,可以通过不同的参数定义多种属性,在使用Compose
的时候,我们也同样可以使用导航组件,类似于单Activity多Fragment的应用,使用Compose
的时候,我们的应用可能是单Activity
多个组合组成的。
为了能够在Compose
中使用导航组件,我们必须加入下面的依赖:
implementation "androidx.navigation:navigation-compose:2.4.2"
NavController
在之前的学习中,我们使用导航组件的时候,最长使用的就是NavHostController
,我们会通过这个对象绑定导航图,执行页面的跳转和回退等操作。NavHostController
继承自NavController
,这是导航组件的核心API,这个API是有状态的,可以跟踪组成应用屏幕的可组合项的返回堆栈以及每个屏幕的状态。
我们可以在可组合项中使用rememberNavController
方法来创建NavController
,同时注意应该明确创建NavHostController
的位置,从而方便需要使用导航的可组合项都可以访问到这个对象:
val controller = rememberNavController()
NavHost
每个NavController
都必须和一个NavHost
可组合项相关联,NavHost
的作用是将NavController
和导航图相关联,导航图则是用于指定我们可以在其中进行导航的可组合项目的地。当我们在可组合项之间进行导航时,NavHost
中的内容会自动进行重组。导航图中的每个可组合项目的地都与一个路线相关联。路线使用String
类型定义,用来指定可组合项的路径。我们可以将其视为指向特定目的地的深层隐式链接,每个目的地都应该有一条唯一的路线。
我们需要只用之前获取到的NavController
来创建NavHost
,同时需要向其设置起始目的地的路线,最后使用composable()
方法向导航结构中添加内容,如下面的代码所示,我们在其中创建了一个NavHost
:
@Composable
fun testNav(){
rememberNavController().apply {
NavHost(navController = this, graph = this.createGraph("home"){
composable("home"){
HomeCompose(controller = this@apply)
}
composable("login"){
LoginCompose(navController = this@apply)
}
composable("profile"){
ProfileCompose(controller = this@apply)
}
})
}
}
上面的代码就创建了一个NavHost
以及相关的导航图,从上面的描述和代码,联系之前的导航组件中相关的知识,我们可以明白,要想创建NavHost
需要以下几步:
- 首先必须要有
NavController
,我们可以在可组合项中通过rememberNavController()
来获取它 - 其次我们需要起始目的地的路线,这里所谓的路线,其实就是我们之前学习导航组件时使用到的id,也就是之前在xml文件中设置的
startDestination
属性,用于标记当前NavHost
的起始组合 NavHost
使用Navigation Kotlin DSL
语法来创建导航图,在导航组件中我们使用NavController
的createGraph
来创建每一个子项- 在Compose中我们使用的是
NavGraphBuilder.composable()
来创建子项
查看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)
}
}
)
}
这是NavGraphBuilder
的扩展函数,我们至少需要向其中传递route
和content
这两个参数,其中route
就是页面(组合)的名称,我们可以通过这个名称找到这个组合,从而进行导航。content
则是我们需要导航到的页面(组合),这是标记了@composable
的函数,我们只需要将组合传递进来即可。
使用导航
和之前的导航组件一样,如果我们需要导航到某一个目的地,我们仍然需要调用navigate()
方法,并向其中传递目的地的唯一路线(id)信息,即可导航到对应的组合上,如下面的代码所示:
navController.navigate(RouteConfig.ROUTE_PROFILE) {
this.popUpTo(RouteConfig.ROUTE_LOGIN){
this.inclusive = true
}
this.anim {
this.enter = R.anim.fragment_enter
this.exit = R.anim.fragment_exit
this.popEnter = R.anim.fragment_pop_enter
this.popExit = R.anim.fragment_pop_exit
}
}
在上面的代码中我们就通过navigate()
跳转到了新的组合,并且会销毁当前组合,这样从新的组合返回的时候是看不到当前组合的。另外需要注意的是,我们只能在回调中使用导航操作,避免在可组合项中直接使用导航操作,否则会造成每次可组合项发生重组都会调用导航。
使用参数
- 在目的地之间跳转的时候经常需要携带参数,在Compose的可组合项目的地之间跳转的时候,如果需要携带参数,那么首先需要定义参数,这种情况下,我们就必须使用路径中的参数占位符,这类似于之前说的在深层导航中添加参数。
下面的代码演示了我们需要在跳转到一个目的地之前需要获取上一个目的地传递的参数:
const val ROUTE_PROFILE = "profile"
composable("${RouteConfig.ROUTE_PROFILE}/{userName}"){
ProfileCompose(controller = controller)
}
上面的代码中我们在设置导航route的时候,指定了需要传递的参数的占位符,后面我们就可以通过向这个占位符传递参数了。
- 默认情况下,所有的参数都将被解析为
String
类型,如果我们希望更改其中的参数类型,则可以通过设置arguments
来设置:
composable(
"${RouteConfig.ROUTE_PROFILE}/{userName}/{age}",
arguments = listOf(navArgument("userName") {
this.type = NavType.StringType
}, navArgument("age") {
this.type = NavType.IntType
})
) {
ProfileCompose(controller = controller)
}
在上面的代码中我们指定了两个参数,一个是userName
,类型为String
,一个是age
,类型为Int
,和导航组件中介绍的一样,这里同样支持导航组件中定义的几种数据类型。
- 定义完参数之后,我们就可以获取参数了
我们应该使用NavBackStackEntry
来获取其它可组合项目的地传递的参数,这个对象是在我们定义目的地的时候就可以获取到的:
composable(
"${RouteConfig.ROUTE_PROFILE}/{userName}/{age}",
arguments = listOf(navArgument("userName") {
this.type = NavType.StringType
}, navArgument("age") {
this.type = NavType.IntType
})
) {
val name = it.arguments?.getString("userName")
val age = it.arguments?.getInt("age")
ProfileCompose(controller = controller,name?: "",age ?: 0)
}
这样我们就可以在新的目的地获取到数据了。
- 最后我们需要到导航的时候设置参数
我们只需要在跳转的时候将参数设置到路径上就可以了,如下面的代码所示:
controller.navigate("${RouteConfig.ROUTE_PROFILE}/unknow/0")
运行上面的代码,在profile
可组合项就可以获取到unknow
和0
这两个数据了。
使用可选参数
在上面我们将参数添加到路径部分,这会造成两个问题:一个是路径会参与与目的地的匹配,如果我们的路径书写错误将会引起应用崩溃,因为无法找到对应的目的地,另外一个是不够灵活,如果我们并不是每次都要传递某一个参数的话,那么我们还是要向其传递一个数据,否则仍然会造成无法找到目的地。
这个时候我们就可以使用可选参数,可选参数和路径参数的不同之处如下:
- 可选参数必须使用查询参数语法
?argName={argName}
定义,传递参数的时候使用?argName={argValue}
- 可选参数必须具有
defaultVlue
或者nullability = true
的定义
对可选参数设置类型和路径参数是一样的,下面的代码演示了在进行目的地跳转的时候携带可选参数:
composable(
"${RouteConfig.ROUTE_PROFILE}/{userName}/{age}?userId={userId}",
arguments = listOf(navArgument("userName") {
this.type = NavType.StringType
}, navArgument("age") {
this.type = NavType.IntType
}, navArgument("userId"){
this.type = NavType.StringType
//设置此值,不传递usrId则按null处理
//this.nullable = true
//设置此值,不传递userId则按1234处理
this.defaultValue = "1234"
})
) {
val name = it.arguments?.getString("userName")
val age = it.arguments?.getInt("age")
val id = it.arguments?.getString("userId")
ProfileCompose(controller = controller, name ?: "", age ?: 0,id)
}
通过上面设置的参数,我们就可以使用可选参数了,及时在跳转的时候不传递可选参数的值,也能够正常执行导航,不会出现应用崩溃的情况。
运行上面的程序,可以看到下面的效果