Jetpack Compose 的 Navigation学习

1. 概述

Navigation 是 Jetpack 用于 Android 导航的组件,作用是处理页面跳转,以及页面跳转过程中的交互。

使用 Navigation,你就需要为每个页面设定一条唯一路径,它是一个 String 常量,形式是 DeepLink 的样子,从一个页面跳转到另一个页面,它通过输入目的地的路径进行转跳。

Navigation 也支持上 Compose,我们可以在 Compose 中使用它来进行跳转。

因为官网和所看到的demo都是把它作为跳转能力使用,所以不清楚 Compose 是否还有别的跳转方式…

2. Navgation api

2.1 使用 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(/*...*/) }
    /*...*/
}

2.2 composable()

上面在 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"))
}

它一般有三个参数,下面逐一解释。

2.2.1 注释1:id

表示页面的路径,是页面的导航标识,是 String 类型。

可以通过 {...}设置占位符,例如例子里面的 userId。

还可以扩展其能力,做为 deeplink,例如跳转到 web 页,具体将在下面讲解。

2.2.2 注释2: arguments

导航目的地的可组合项所需参数,是可选参数, 需要使用 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 设置别的可选参数,例如 defaultValuenullability = true(默认值可空):

navArgument("userId") {
    
     default = "123456" }

2.2.3 注释3:lambda表达式

它是一个用于构造一个可组合项,表示页面内容。看起来是一个 (NavBackStackEntry) -> @Compsable() 类型。

NavBackStackEntry 表示回退栈信息, 它的 argument 携带了打开页面所需要的参数,我们可以使用它来提取参数,然后构造可组合项:

composable("profile/{userId}") {
    
     backStackEntry ->
    Profile(navController, backStackEntry.arguments?.getString("userId"))
}

2.3 页面跳转

通过调用 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 指南

2.4 DeepLink

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 文件添加相应的 <intent-filter> 元素,使得外部可以访问。如需启用上述深层链接,可以在清单的 <activity> 元素中添加以下内容:

<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)
}

2.5 嵌套结构

一个 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 进行导航

猜你喜欢

转载自blog.csdn.net/rikkatheworld/article/details/126191391