设计导航图

导航组件 使用一个 导航图 来管理应用的导航。导航图是一种数据结构,其中包含应用中的每个目标以及它们之间的连接。

目标类型

目标主要有三种类型:托管、对话框和活动。下表概述了这三种目标类型及其用途。

类型

描述

用例

托管

填充整个导航主机。也就是说,托管目标的大小与导航主机的大小相同,并且先前目标不可见。

主屏幕和详情屏幕。

对话框

显示覆盖 UI 组件。此 UI 与导航主机的定位或大小无关。先前目标在目标下方可见。

警报、选择、表单。

活动

表示应用中的唯一屏幕或功能。

充当导航图的退出点,启动一个新的 Android 活动,该活动与导航组件分开管理。

在现代 Android 开发中,一个应用由一个活动组成。因此,当与第三方活动交互或作为 迁移过程 的一部分时,最好使用活动目标。

本文档包含托管目标的示例,托管目标是最常见和最基本的目标。有关其他目标的信息,请参阅以下指南

框架

虽然在每种情况下都适用相同的一般工作流程,但创建导航主机和图的确切方式取决于您使用的 UI 框架。

  • Compose: 使用 NavHost 可组合项。使用 Kotlin DSLNavGraph 添加到其中。您可以通过两种方式创建图
    • 作为 NavHost 的一部分: 在添加 NavHost 时直接构建导航图。
    • 以编程方式: 使用 NavController.createGraph() 方法创建 NavGraph 并将其直接传递给 NavHost
  • 片段: 当使用带有视图 UI 框架的片段时,请使用 NavHostFragment 作为宿主。有多种方法可以创建导航图
    • 以编程方式: 使用 Kotlin DSL 创建 NavGraph 并将其直接应用于 NavHostFragment
      • 用于片段和 Compose 的 Kotlin DSL 中使用的 createGraph() 函数是相同的。
    • XML: 直接在 XML 中编写导航宿主和图。
    • Android Studio 编辑器: 使用 Android Studio 中的 GUI 编辑器以 XML 资源文件的形式创建和调整导航图。

Compose

在 Compose 中,使用可序列化对象或类来定义一个 *路由*。路由描述了如何到达目标,并包含目标所需的所有信息。定义好路由后,使用 NavHost 可组合函数来创建导航图。请考虑以下示例

@Serializable
object Profile
@Serializable
object FriendsList

val navController = rememberNavController()

NavHost(navController = navController, startDestination = Profile) {
    composable<Profile> { ProfileScreen( /* ... */ ) }
    composable<FriendsList> { FriendsListScreen( /* ... */ ) }
    // Add more destinations similarly.
}
  1. 一个可序列化对象表示两个路由中的每一个,ProfileFriendsList
  2. NavHost 可组合函数的调用传递了一个 NavController 和起始目标的路由。
  3. 传递给 NavHost 的 lambda 最终会调用 NavController.createGraph() 并返回一个 NavGraph
  4. 每个路由都作为类型参数提供给 NavGraphBuilder.composable<T>(),它将目标添加到生成的 NavGraph 中。
  5. 传递给 composable 的 lambda 是 NavHost 为该目标显示的内容。

了解 lambda

为了更好地理解创建 NavGraph 的 lambda,请考虑,为了构建与前面代码片段相同的图,可以单独使用 NavController.createGraph() 创建 NavGraph,并将其直接传递给 NavHost

val navGraph by remember(navController) {
  navController.createGraph(startDestination = Profile)) {
    composable<Profile> { ProfileScreen( /* ... */ ) }
    composable<FriendsList> { FriendsListScreen( /* ... */ ) }
  }
}
NavHost(navController, navGraph)

传递参数

如果需要将数据传递给目标,请使用具有参数的类定义路由。例如,Profile 路由是一个具有 name 参数的数据类。

@Serializable
data class Profile(val name: String)

每当需要将参数传递给该目标时,都会创建一个路由类的实例,并将参数传递给类构造函数。

对于可选参数,创建具有默认值的空值字段。

@Serializable
data class Profile(val nickname: String? = null)

获取路由实例

可以使用 NavBackStackEntry.toRoute()SavedStateHandle.toRoute() 获取路由实例。使用 composable() 创建目标时,NavBackStackEntry 可作为参数使用。

@Serializable
data class Profile(val name: String)

val navController = rememberNavController()

NavHost(navController = navController, startDestination = Profile(name="John Smith")) {
    composable<Profile> { backStackEntry ->
        val profile: Profile = backStackEntry.toRoute()
        ProfileScreen(name = profile.name) }
}

请注意此代码片段中的以下内容

  • Profile 路由指定导航图中的起始目标,"John Smith" 作为 name 的参数。
  • 目标本身是 composable<Profile>{} 代码块。
  • ProfileScreen 可组合函数获取 profile.name 的值作为其自己的 name 参数。
  • 因此,值 "John Smith" 传递给 ProfileScreen

最小示例

NavControllerNavHost 协同工作的完整示例

@Serializable
data class Profile(val name: String)

@Serializable
object FriendsList

// Define the ProfileScreen composable.
@Composable
fun ProfileScreen(
    profile: Profile
    onNavigateToFriendsList: () -> Unit,
  ) {
  Text("Profile for ${profile.name}")
  Button(onClick = { onNavigateToFriendsList() }) {
    Text("Go to Friends List")
  }
}

// Define the FriendsListScreen composable.
@Composable
fun FriendsListScreen(onNavigateToProfile: () -> Unit) {
  Text("Friends List")
  Button(onClick = { onNavigateToProfile() }) {
    Text("Go to Profile")
  }
}

// Define the MyApp composable, including the `NavController` and `NavHost`.
@Composable
fun MyApp() {
  val navController = rememberNavController()
  NavHost(navController, startDestination = Profile(name = "John Smith")) {
    composable<Profile> { backStackEntry ->
        val profile: Profile = backStackEntry.toRoute()
        ProfileScreen(
            profile = profile,
            onNavigateToFriendsList = {
                navController.navigate(route = FriendsList)
            }
        )
    }
    composable<FriendsList> {
      FriendsListScreen(
        onNavigateToProfile = {
          navController.navigate(
            route = Profile(name = "Aisha Devi")
          )
        }
      )
    }
  }
}

如代码片段所示,不要将 NavController 传递给可组合函数,而是向 NavHost 公开一个事件。也就是说,可组合函数应该有一个类型为 () -> Unit 的参数,NavHost 为其传递一个调用 NavController.navigate() 的 lambda。

片段

如前面各节所述,当使用片段时,可以选择使用 Kotlin DSL、XML 或 Android Studio 编辑器以编程方式创建导航图。

以下各节详细介绍了这些不同的方法。

以编程方式

Kotlin DSL 提供了一种以编程方式创建带有片段的导航图的方法。在许多方面,这比使用 XML 资源文件更简洁、更现代。

请考虑以下示例,它实现了一个双屏导航图。

首先需要创建 NavHostFragment,它 *不能* 包含 app:navGraph 元素

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</FrameLayout>

接下来,将 NavHostFragmentid 传递给 NavController.findNavController。这将 NavControllerNavHostFragment 关联起来。

随后,对 NavController.createGraph() 的调用将图链接到 NavController,从而也链接到 NavHostFragment

@Serializable
data class Profile(val name: String)

@Serializable
object FriendsList

// Retrieve the NavController.
val navController = findNavController(R.id.nav_host_fragment)

// Add the graph to the NavController with `createGraph()`.
navController.graph = navController.createGraph(
    startDestination = Profile(name = "John Smith")
) {
    // Associate each destination with one of the route constants.
    fragment<ProfileFragment, Profile> {
        label = "Profile"
    }

    fragment<FriendsListFragment, FriendsList>() {
        label = "Friends List"
    }

    // Add other fragment destinations similarly.
}

以这种方式使用 DSL 与前面关于 Compose 的部分中概述的工作流程非常相似。例如,在那里和这里,NavController.createGraph() 函数都会生成 NavGraph。同样,虽然 NavGraphBuilder.composable() 将可组合目标添加到图中,但这里 NavGraphBuilder.fragment() 添加了一个片段目标。

有关如何使用 Kotlin DSL 的更多信息,请参阅 使用 NavGraphBuilder DSL 构建图

XML

您可以直接编写 XML。以下示例反映了并等同于上一节中的双屏示例。

首先,创建一个 NavHostFragment。它充当包含实际导航图的导航宿主。

NavHostFragment 的最小实现

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:navGraph="@navigation/nav_graph" />

</FrameLayout>

NavHostFragment 包含属性 app:navGraph。使用此属性将导航图连接到导航宿主。以下是如何实现图的示例

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/nav_graph"
    app:startDestination="@id/profile">

    <fragment
        android:id="@+id/profile"
        android:name="com.example.ProfileFragment"
        android:label="Profile">

        <!-- Action to navigate from Profile to Friends List. -->
        <action
            android:id="@+id/action_profile_to_friendslist"
            app:destination="@id/friendslist" />
    </fragment>

    <fragment
        android:id="@+id/friendslist"
        android:name="com.example.FriendsListFragment"
        android:label="Friends List" />

    <!-- Add other fragment destinations similarly. -->
</navigation>

使用操作来定义不同目标之间的连接。在此示例中,profile 片段包含一个导航到 friendslist 的操作。有关更多信息,请参阅 使用导航操作和片段

编辑器

可以使用 Android Studio 中的导航编辑器管理应用的导航图。这本质上是一个 GUI,可用于创建和编辑 NavigationFragment XML,如上一节所示。

有关更多信息,请参阅 导航编辑器

嵌套图

还可以使用嵌套图。这涉及将图用作导航目标。有关更多信息,请参阅 嵌套图

进一步阅读

有关更多核心导航概念,请参阅以下指南

  • 概述: 确保阅读导航组件的总体概述。
  • 活动目标: 如何实现将用户带到活动的示例。
  • 对话框目标: 如何创建将用户带到对话框的示例。
  • 导航到目标: 涵盖如何从一个目标导航到另一个目标的详细指南。
  • 嵌套图: 关于如何在另一个导航图中嵌套一个导航图的深入指南。