Navigation
是 Jetpack 用于 Android 导航的组件,作用是处理页面跳转,以及页面跳转过程中的交互。
使用 Navigation
,你就需要为每个页面设定一条唯一路径,它是一个 String 常量,形式是 DeepLink 的样子,从一个页面跳转到另一个页面,它通过输入目的地的路径进行转跳。
Navigation
也支持上 Compose,我们可以在 Compose 中使用它来进行跳转。
因为官网和所看到的demo都是把它作为跳转能力使用,所以不清楚 Compose 是否还有别的跳转方式…
NavHost
NavHost
可以用来描述导航图,就是可以在上面指定那些可以跳转的页面(像是注册路由一样),如果你需要从一个页面跳到另一个页面,你需要改变路线, NavHost
会触发重组,然后跳转到目的地。
NavHost
的重组是由 NavController
触发的,它代表了 NavHost
的状态, 是 Navigation 在 Compose 的中心 api, 它有两个作用:
我们通过下面方式创建它:
val navController = rememberNavController()
要使用 NavHost
, 必须要绑定一个 NavController
,并且通过 startDestination
设置一个默认起始页面:
NavHost(navController = navController, startDestination = "profile") {
composable("profile") { Profile(/*...*/) }
composable("friendslist") { FriendsList(/*...*/) }
/*...*/
}
上面在 NavHost
中填入的 composable 是什么呢?
composable(..) { ... }
是 Navigation 提供的 DSL,它可以让我们添加可导航的页面,一个 composable() 可以视为注册一个可导航页面,就像下面这样:
// 为 Profile 赋予导航能力,相当于注册到路由表上
composable(
"profile?userId={userId}", // 1
arguments = listOf(navArgument("userId") { defaultValue = "me" }) // 2
) { backStackEntry -> // 3
Profile(navController, backStackEntry.arguments?.getString("userId"))
}
它一般有三个参数,下面逐一解释。
表示页面的路径,是页面的导航标识,是 String 类型。
可以通过 {...}
设置占位符,例如例子里面的 userId。
还可以扩展其能力,做为 deeplink,例如跳转到 web 页,具体将在下面讲解。
导航目的地的可组合项所需参数,是可选参数, 需要使用 NavArgument
组成的列表携带。
默认情况下,所有参数都会作为字符串被解析, 我们可以通过 type设置。
NavHost(startDestination = "profile/{userId}/{age}") {
...
composable(
"profile/{userId}/{age}",
arguments = listOf(
navArgument("userId") { type = NavType.StringType }, // 这里可以不用写,因为默认就是字符串
navArgument("age") { type = NavType.IntType } // 需要显式设置为 Int 类型
)
) {...}
}
我们还可以为 navArgument
设置别的可选参数,例如 defaultValue
或 nullability = true
(默认值可空):
navArgument("userId") { default = "123456" }
它是一个用于构造一个可组合项,表示页面内容。看起来是一个 (NavBackStackEntry) -> @Compsable()
类型。
NavBackStackEntry
表示回退栈信息, 它的 argument
携带了打开页面所需要的参数,我们可以使用它来提取参数,然后构造可组合项:
composable("profile/{userId}") { backStackEntry ->
Profile(navController, backStackEntry.arguments?.getString("userId"))
}
通过调用 navController.navgate(..)
进行导航, 参数是目的地的标识,如下所示:
@Composable
fun Profile(navController: NavController) {
/*...*/
Button(onClick = { navController.navigate("friendslist") }) {
Text(text = "Navigate next")
}
/*...*/
}
需要注意的是,我们需要在回调中才能使用它,避免在可组合项中直接使用,避免重组时调用了它。
如果我们需要携带参数,可以直接填入:
navController.navigate("profile/user1234")
navController.navigate("profile/${getUid()}")
默认情况下, navigate
会将新目的地添加到回退栈中,我们可以通过添加别的参数来改变这个行为:
// 在进入"friendsList"之前,回退栈会弹出所有的可组合项,直到 "home"
navController.navigate("friendslist") {
popUpTo("home")
}
// 在进入"friendsList"之前,回退栈会弹出所有的可组合项,直到 "home",并且包括它
navController.navigate("friendslist") {
popUpTo("home") { inclusive = true }
}
// 对应 Android 的 SingleTop,如果回退栈顶部已经是 "search",就不会重新创建
navController.navigate("search") {
launchSingleTop = true
}
更多能力请参考: popUpTo 指南
Navigation Compose 支持隐式深层链接,此类链接也可定义为 composable() 函数的一部分。使用 navDeepLink() 以列表的形式添加深层链接。
例如下面代码中定义的导航项,它将页面参数拼接到 deepLink:
val uri = "https://www.example.com"
composable(
"profile?id={id}",
deepLinks = listOf(navDeepLink { uriPattern = "$uri/{id}" })
) { backStackEntry ->
Profile(navController, backStackEntry.arguments?.getString("id"))
}
借助这些深层链接,可以将特定的网址、操作和/或 MIME 类型与可组合项关联起来。
我们可以在 manifest.xml 文件添加相应的
元素,使得外部可以访问。如需启用上述深层链接,可以在清单的
元素中添加以下内容:
<activity …>
<intent-filter>
...
<data android:scheme="https" android:host="www.example.com" />
</intent-filter>
</activity>
当其他应用触发该深层链接时,Navigation 会自动深层链接到相应的可组合项。
这些深层链接还可用于构建包含可组合项中的相关深层链接的 PendingIntent
:
val id = "exampleId"
val context = LocalContext.current
val deepLinkIntent = Intent(
Intent.ACTION_VIEW,
"https://www.example.com/$id".toUri(),
context,
MyActivity::class.java
)
val deepLinkPendingIntent: PendingIntent? = TaskStackBuilder.create(context).run {
addNextIntentWithParentStack(deepLinkIntent)
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
}
一个 NavHost
内注册的可组合项看起来是平铺的,没有深层结构。 我们可以通过 navigation
函数为其增加嵌套内容。
例如下面定义一个导航结构, “login” 可组合项又可以导航到 “username”、“password”、“registration” 这三个可组合项,而不让别的与 login 同级的可组合项进入,我们可以这样写:
NavHost(navController, startDestination = "home") {
...
// 通过 route 来为'login' 定义一个更深层次的导航图
// 因此'username'封装了图的内部路由逻辑
navigation(route = "login", startDestination = "username") {
composable("username") { ... }
composable("password") { ... }
composable("registration") { ... }
}
...
}
官方建议,如果在导航图比较大时,将其作为扩展函数拆分一下,以调高代码可读性:
fun NavGraphBuilder.loginGraph(navController: NavController) {
navigation(startDestination = "username", route = "login") {
composable("username") { ... }
composable("password") { ... }
composable("registration") { ... }
}
}
// 使用:
NavHost(navController, startDestination = "home") {
...
loginGraph(navController)
...
}
使用 Compose 进行导航