Google 致力于为黑人社区推动种族平等。查看具体举措

使用 Compose 进行导航

Navigation 组件支持 Jetpack Compose 应用。您可以在利用 Navigation 组件的基础架构和功能的同时,在可组合项之间导航。

设置

如需支持 Compose,请在应用模块的 build.gradle 文件中使用以下依赖项:

dependencies {
    def nav_compose_version = "1.0.0-alpha01"
    implementation "androidx.navigation:navigation-compose:$nav_compose_version"
}

使用入门

NavController 是 Navigation 组件的中心 API。此 API 是有状态的,可以跟踪组成应用屏幕的可组合项的返回堆栈以及每个屏幕的状态。

您可以通过在可组合项中使用 rememberNavController() 方法来创建 NavController

val navController = rememberNavController()

您应该在可组合项层次结构中的适当位置创建 NavController,使所有需要引用它的可组合项都可以访问它。这遵循状态提升的原则,并且允许您使用 NavController 及其通过 currentBackStackEntryAsState() 提供的状态作为更新屏幕外的可组合项的可信来源。有关此功能的示例,请参阅与底部导航栏集成

创建 NavHost

每个 NavController 都必须与一个 NavHost 可组合项相关联。NavHostNavController 与导航图相关联,后者用于指定您应能够在其间进行导航的可组合项目的地。当您在可组合项之间进行导航时,NavHost 的内容会自动进行重组。导航图中的每个可组合项目的地都与一个路线相关联。

如需创建 NavHost,您需要使用之前通过 rememberNavController() 创建的 NavController,以及导航图的起始目的地的路线。NavHost 创建使用 Navigation Kotlin DSL 中的 lambda 语法来构建导航图。您可以使用 composable() 方法向导航结构添加内容。此方法需要您提供一个路线以及应关联到相应目的地的可组合项:

NavHost(navController, startDestination = "profile") {
    composable("profile") { Profile(...) }
    composable("friendslist") { FriendsList(...) }
    ...
}

如需导航到导航图中的可组合项目的地,您必须使用 navigate() 方法。navigate() 接受代表目的地路线的单个 String 参数。如需从导航图中的某个可组合项进行导航,请调用 navigate()

fun Profile(navController: NavController) {
    ...
    Button(onClick = { navController.navigate("friends") }) {
        Text(text = "Navigate next")
    }
    ...
}

您应仅在回调中调用 navigate(),而不能在可组合项本身中调用它,以避免每次重组时都调用 navigate()

Navigation Compose 还支持在可组合项目的地之间传递参数。为此,您需要向路线中添加参数占位符,就像在使用基础导航库时向深层链接中添加参数一样。

NavHost(startDestination = "profile/{userId}") {
    ...
    composable("profile/{userId}") {...}
}

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

NavHost(startDestination = "profile/{userId}") {
    ...
    composable(
        "profile/{userId}",
        arguments = listOf(navArgument("userId") { type = NavType.StringType })
    ) {...}
}

您应该从 composable() 函数的 lambda 中提供的 NavBackStackEntry 中提取 NavArguments

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

若要将参数传递到目的地,您需要在 navigate 调用中添加路线值而不是占位符:

navController.navigate("profile/user1234")

如需查看支持的类型的列表,请参阅在目的地之间传递数据

添加可选参数

Navigation Compose 还支持可选的导航参数。可选参数与必需参数有以下两点不同:

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

这意味着,所有可选参数都必须以列表的形式显式添加到 composable() 函数:

composable(
    "profile?userId={userId}",
    arguments = listOf(navArgument("userId") { defaultValue = "me" })
) { backStackEntry ->
    Profile(navController, backStackEntry.arguments?.getString("userId"))
}

现在,即使没有向目的地传递任何参数,系统也会使用“me”的 defaultValue

通过路线处理参数的结构意味着可组合项将完全独立于 Navigation,并且更易于测试。

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

val uri = "https://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 = ...
val context = ContextAmbient.current
val deepLinkIntent = Intent(
    Intent.ACTION_VIEW,
    "https://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 在相应深层链接目的地打开您的应用。

与底部导航栏集成

通过在可组合项层次结构中的更高层级定义 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,并利用该条目,使用 NavHostController 所含的 KEY_ROUTE 常量从各参数检索相应路线。使用此路线确定所选项是否为正确的目的地,并通过设置标签、突出显示相应的项和导航(如果路线不匹配)来进行适当的响应。

val navController = rememberNavController()
Scaffold(
    bottomBar = {
        BottomNavigation {
            val navBackStackEntry by navController.currentBackStackEntryAsState()
            val currentRoute = navBackStackEntry?.arguments?.getString(KEY_ROUTE)
            items.forEach { screen ->
                BottomNavigationItem(
                    icon = { Icon(Icons.Filled.Favorite) },
                    label = { Text(stringResource(screen.resourceId)) },
                    selected = currentRoute == screen.route,
                    onClick = {
                        // This is the equivalent to popUpTo the start destination
                        navController.popBackStack(navController.graph.startDestination, false)

                        // This if check gives us a "singleTop" behavior where we do not create a
                        // second instance of the composable if we are already on that destination
                        if (currentRoute != screen.route) {
                            navController.navigate(screen.route)
                        }
                    }
                )
            }
        }
    }
) {

    NavHost(navController, startDestination = Screen.Profile.route) {
        composable(Screen.Profile.route) { Profile(navController) }
        composable(Screen.FriendsList.route) { FriendsList(navController) }
    }
}

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

测试

强烈建议您将 Navigation 代码与可组合项目的地分离开,以便独立于 NavHost 可组合项单独测试每个可组合项。

借助 composable lambda 提供的间接层,您可以将 Navigation 代码与可组合项本身分离开。这在以下两个方向上可行:

  • 仅将解析后的参数传递到可组合项
  • 传递应由要导航的可组合项触发的 lambda,而不是 NavController 本身。

例如,如果某个 Profile 可组合项接受 userId 作为输入,并允许用户导航到好友的个人资料页面,则可以采用以下签名:


@Composable
fun Profile(
    userId: String,
    navigateToFriendProfile: (friendUserId: String) -> Unit
) {
 …
}

在这里,我们看到 Profile 可组合项可独立于 Navigation 运行,从而可单独进行测试。composable lambda 会封装弥合 Navigation API 与您的可组合项之间的差距所需的基本逻辑:

composable(
    "profile?userId={userId}",
    arguments = listOf(navArgument("userId") { defaultValue = "me" })
) { backStackEntry ->
    Profile(backStackEntry.arguments?.getString("userId")) { friendUserId ->
        navController.navigate("profile?userId=$friendUserId")
}

了解详情

如需详细了解 Jetpack 导航,请参阅 Navigation 组件使用入门