跳转至

导航

设置

想要使用导航,需要引入信赖

implementation "androidx.navigation:navigation-compose:2.4.0-rc01"

使用入门

NavController 是 Navigaiton 的核心,是有状态的,可以跟踪返回堆栈以及每个界面的状态。可以通过 rememberNavController 来创建

val navController = rememberNavController()

NavHost 是导航容器,NavHost 将 NavController 与导航图相关联,NavController 能够在所有页面之间进行跳转。当在进行页面跳转时,NavHost 的内容会自动进行重组。导航图中的目的地就是一个路由。路由名称通常是一个字符串。

创建 NavHost 需要使用到之前通过 rememberNavController() 创建的 NavController ,以及导航图的起始目的地路由。NavHost 使用 Kotlin DSL 中 lamada 语法来构建导航图。可以在lamada 表达式中使用 composable() 方法向导航结构中添加内容。

NavHost(navController = navController, startDestination = "First") {
    composable("First") { FirstScreen(/*...*/) }
    composable("Second") { SecondScreen(/*...*/) }
    composable("Third") { ThirdScreen(/*...*/) }
    /*...*/
}

页面跳转

进行页面跳转,需要使用 navController 的 navigate() 方法,默认情况下,navigate() 会将新目的地添加到返回堆栈中。可以通过builder参数来增加或修改一些行为

//在跳转到 Second 之前 ,关掉所有页面直到 First
navController.navigate("Second"){
    popUpTo("First")
}

//在跳转到 Second 之前 ,关掉所有页面直到 First,包括 First 页面
navController.navigate("Second"){
    popUpTo("First"){ inclusive = true }
}

//在跳转到 Second  且只允许出现一个页面
navController.navigate("Second"){
    launchSingleTop = true
}

带参导航

Navigation Compose 支持在页面之间进行参数传递,需要在导航路由中添加参数占位符。

NavHost(navController = navController, startDestination = "First") {
    composable("First") { FirstScreen(/*...*/) }
    composable("Second/{param1}") { SecondScreen(/*...*/) }
    composable("Third") { ThirdScreen(/*...*/) }
    /*...*/
}

默认情况下,所有参数都会解析为字符串类型。可以使用 arguments 参数来设置type,以指定其他类型

NavHost(navController = navController, startDestination = "First") {
    composable("First") { FirstScreen(/*...*/) }
    composable("Second/{param1}", arguments = listOf(navArgument("param1"){ type = NavType.StringType })) 
        { SecondScreen(/*...*/) }
    composable("Third") { ThirdScreen(/*...*/) }
    /*...*/
}

从 composable 方法的 lamada 中提供的 NavBackStackEntry 中提取参数

composable("Second/{param1}", arguments = listOf(navArgument("param1"){ type = NavType.StringType })) 
{ backStackEntry ->
    val param = backStackEntry.arguments?.getString("param1")
    SecondScreen(/*...*/) 
}

在进行页面跳转时需要传递参数

navController.navigate("Second/这是参数值")

可选参数

上面传递的参数为必传参数,Navigation Compose还支持可选参数。可选参数和必传参数有以下两点不同:

  • 可选参数必须使用查询参数语法?argName={argName} 来添加
  • 可选参数必须具有 defaultValuenullability = true (将默认值设置为 null)

这意味着,所有可选参数都必须以及列表的形式显式添加到 composable 方法中

composable(
    "Second?param1={param1}",
    arguments = listOf(navArgument("param1"){ defaultValue = "Default Value" })
) { backStackEntry ->
    val param = backStackEntry.arguments?.getString("param1")
    SecondScreen(/*...*/) 
}

即使没有传递任何参数,系统也会使用 Default Value 来作为参数值传递到目的地页面

完整示例

假设有三个界面FirstScreenSecondScreenThirdScreen,需要从 First 跳到 Second 再跳到 Third。然后从 Third 直接回到 First,我们给每一个 Composable增加一个 onTap 回调参数

@Composable
fun NavHostSample() {
    val navController = rememberNavController() //获取到导航控制器

    //使用 NavHost 放置页面,进行路由管理
    //startDestination 设置起始显示页面,为了便于管理,可以声明一个静态类来对各个页面的路由名称进行管理
    //在 NavGraphBuilder里使用 Composable 时需要使用composable
    NavHost(navController = navController, startDestination = Screen.First.route) {

        composable(Screen.First.route) {
            FirstScreen() {
                navController.navigate("${Screen.Second.route}/11111/22222")
            }
        }

        composable(
            "${Screen.Second.route}/{${Param.SecondScreenParam1.name}}/{${Param.SecondScreenParam2.name}}",
        ) {
            val value1 =
                it.arguments?.getString(Param.SecondScreenParam1.name, "") ?: "Default Value"

            val value2 =
                it.arguments?.getString(Param.SecondScreenParam2.name, "") ?: "Default Value"

            SecondScreen(value1, value2) {
                navController.navigate(Screen.Third.route)
            }
        }

        composable("${Screen.Third.route}?${Param.ThirdScreenParam1.name}={${Param.ThirdScreenParam1.name}}", arguments = listOf(
            navArgument(Param.ThirdScreenParam1.name){defaultValue = "Default Value1"})) {
            val value1 =
                it.arguments?.getString(Param.ThirdScreenParam1.name, "") ?: "Default Value2"

            ThirdScreen(value1) {
                navController.popBackStack(Screen.First.route, false)
            }
        }

    }
}

@Composable
fun FirstScreen(onTap: () -> Unit) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .background(Color.Red),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(text = "First Screen")

        Button(onClick = { onTap() }) {
            Text("Go To Second Screen")
        }
    }
}

@Composable
fun SecondScreen(param1: String, param2: String, onTap: () -> Unit) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .background(Color.Green),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(text = "Second Screen:$param1 and $param2")

        Button(onClick = { onTap() }) {
            Text("Go To Third Screen")
        }
    }
}


@Composable
fun ThirdScreen(param1: String, onTap: () -> Unit) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .background(Color.Blue),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(text = "Third Screen:$param1")

        Button(onClick = { onTap() }) {
            Text("Go Back To First Screen")
        }
    }
}

/**
 * 声明一个静态类来对各个页面的路由名称进行管理
 *
 * @property route
 */
sealed class Screen(val route: String) {
    object First : Screen("First")
    object Second : Screen("Second")
    object Third : Screen("Third")
}

sealed class Param(val name: String) {
    object SecondScreenParam1 : Param("param1")
    object SecondScreenParam2 : Param("param2")
    object ThirdScreenParam1 : Param("param1")
}

@Preview
@Composable
fun NavHostSamplePreview() {
    NavHostSample()
}

深层链接

Navigation Compose 支持隐式深层链接,此类链接也可以定义为 composable() 的一部分。使用 navDeepLink() 以列表形式添加深层链接

val uri = "https://www.bughub.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.bughub.com" />
  </intent-filter>
</activity>

当其他应用触发该深层链接时,Navigation 会自动深层链接到相应的Composable。

这些深层链接还可用于构建包含Composable中的相关深层链接的 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)
}

然后,您可以像使用任何其他 PendingIntent 一样,使用此 deepLinkPendingIntent 在相应深层链接目的地打开您的应用。

嵌套导航结构

您可以将目的地归入一个嵌套图,以便在应用界面中对特定流程进行模块化。例如,您可以对独立的登录流程进行模块化。

嵌套图可以封装其目的地。与根图一样,嵌套图必须具有被其路径标识为起始目的地的目的地。此目的地是当您导航到与嵌套图相关联的路径时所导航到的目的地。

如需向 NavHost 添加嵌套图,您可以使用 navigation 扩展函数:

NavHost(navController, startDestination = "home") {
    ...
    // 当导航到 login 路由时,自动找到 username 目标进行显示
    navigation(startDestination = "username", route = "login") {
        composable("username") { ... }
        composable("password") { ... }
        composable("registration") { ... }
    }
    ...
}

强烈建议您在导航图变大时将其拆分为多个方法。这也允许多个模块提交各自的导航图。

fun NavGraphBuilder.loginGraph(navController: NavController) {
    navigation(startDestination = "username", route = "login") {
        composable("username") { ... }
        composable("password") { ... }
        composable("registration") { ... }
    }
}

通过将该方法设为 NavGraphBuilder 上的扩展方法,您可以将其与预构建的 navigation、composable 和 dialog 扩展方法一起使用:

NavHost(navController, startDestination = "home") {
    ...
    loginGraph(navController)
    ...
}

与底部导航栏集成

通过在可组合项层次结构中的更高层级定义 NavController,您可以将 Navigation 与其他组件(例如 BottomNavBar)相关联。这样,您就可以通过选择底部栏中的图标来进行导航。

如需将底部导航栏中的项与您的导航图中的路线相关联,建议您定义密封的类(例如此处所示的 Screen),其中包含相应目的地的路线和字符串资源 ID。

sealed class Screen(val route: String, @StringRes val resourceId: Int) {
    object Profile : Screen("profile", R.string.profile)
    object FriendsList : Screen("friendslist", R.string.friends_list)
}

然后,将这些项放置在 BottomNavigationItem 可以使用的列表中:

val items = listOf(
   Screen.Profile,
   Screen.FriendsList,
)

在 BottomNavigation 可组合项中,使用 currentBackStackEntryAsState() 函数获取当前的 NavBackStackEntry。此条目允许您访问当前的 NavDestination。然后,可通过 hierarchy 辅助方法将该项的路由与当前目的地及其父目的地的路由进行比较来确定每个 BottomNavigationItem 的选定状态(以处理使用嵌套导航的情况)。

该项目的路由还用于将 onClick lambda 连接到对 navigate 的调用,以便在点按该项时会转到该项。通过使用 saveState 和 restoreState 标志,当您在底部导航项之间切换时,系统会正确保存并恢复该项的状态和返回堆栈。

val navController = rememberNavController()
Scaffold(
  bottomBar = {
    BottomNavigation {
      val navBackStackEntry by navController.currentBackStackEntryAsState()
      val currentDestination = navBackStackEntry?.destination
      items.forEach { screen ->
        BottomNavigationItem(
          icon = { Icon(Icons.Filled.Favorite, contentDescription = null) },
          label = { Text(stringResource(screen.resourceId)) },
          selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
          onClick = {
            navController.navigate(screen.route) {
              // Pop up to the start destination of the graph to
              // avoid building up a large stack of destinations
              // on the back stack as users select items
              popUpTo(navController.graph.findStartDestination().id) {
                saveState = true
              }
              // Avoid multiple copies of the same destination when
              // reselecting the same item
              launchSingleTop = true
              // Restore state when reselecting a previously selected item
              restoreState = true
            }
          }
        )
      }
    }
  }
) { innerPadding ->
  NavHost(navController, startDestination = Screen.Profile.route, Modifier.padding(innerPadding)) {
    composable(Screen.Profile.route) { Profile(navController) }
    composable(Screen.FriendsList.route) { FriendsList(navController) }
  }
}

在这里,您可以利用 NavController.currentBackStackEntryAsState() 方法从 NavHost 函数中获取 navController 状态,并与 BottomNavigation 组件共享此状态。这意味着 BottomNavigation 会自动拥有最新状态。

更多

https://developer.android.com/jetpack/compose/navigation

视频教程