设计导航图

Navigation 组件使用导航图管理应用导航。导航图是一种数据结构,包含应用内的每个目的地以及这些目的地之间的连接。

目的地类型

有三种常规类型的目的地:托管、对话框和 activity。下表概述了这三种目的地类型及其用途。

类型

说明

用例

托管

填充整个导航宿主。也就是说,托管目的地的大小与导航宿主的大小相同,之前的目的地不会显示。

主界面和详情界面。

对话框

显示叠加界面组件。此界面与导航宿主的位置或大小无关。之前的目的地会显示在该目的地下方。

提醒、选择、表单。

活动

表示应用中的独特界面或功能。

充当导航图的退出点,启动与 Navigation 组件分开管理的新 Android activity。

在 Modern Android Development 中,应用由 1 个 activity 组成。因此,当与第三方 activity 互动或作为迁移过程的一部分时,最适合使用 activity 目的地。

本文档包含托管目的地的示例,这些目的地是最常用和最基本的目的地。如需了解其他目的地,请参阅以下指南:

框架

虽然相同的常规工作流适用于所有情况,但导航宿主和导航图的创建方式取决于您使用的界面框架。

  • Compose:使用 NavHost 可组合项。使用 Kotlin DSL 向其添加 NavGraph。您可以通过以下两种方式创建图表:
    • 作为 NavHost 的一部分:在添加 NavHost 的过程中直接构建导航图。
    • 以编程方式:使用 NavController.createGraph() 方法创建 NavGraph 并将其直接传递给 NavHost
  • fragment:将 fragment 与视图界面框架搭配使用时,请使用 NavHostFragment 作为宿主。您可以通过多种方式创建导航图:
    • 以编程方式:使用 Kotlin DSL 创建 NavGraph 并将其直接应用于 NavHostFragment
      • fragment 和 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。

了解 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)

每当需要向该目的地传递参数时,您都需要创建路线类的实例,并将参数传递给类构造函数。

获取路由实例

您可以使用 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")
          )
        }
      )
    }
  }
}

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

fragment

如前几部分所述,在使用 fragment 时,您可以选择使用 Kotlin DSL、XML 或 Android Studio 编辑器以编程方式创建导航图。

以下部分详细介绍了这些不同的方法。

以程序化方式

Kotlin DSL 提供了一种以编程方式创建包含 fragment 的导航图的方法。在许多方面,这比使用 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。这会将 NavController 与 NavHostFragment 相关联。

随后,调用 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() 会添加 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 fragment 包含一项导航到 friendslist 的操作。如需了解详情,请参阅使用导航操作和 fragment

编辑器

您可以使用 Android Studio 中的 Navigation Editor 管理应用的导航图。这在本质上是一个可用于创建和修改 NavigationFragment XML 的 GUI,如上一部分所示。

如需了解详情,请参阅导航编辑器

嵌套图

您还可以使用嵌套图。这涉及使用图表作为导航目的地。如需了解详情,请参阅嵌套图

深入阅读

如需了解更多核心导航概念,请参阅以下指南:

  • 概览请务必阅读 Navigation 组件的一般概览。
  • activity 目的地有关如何实现将用户转到 activity 的目的地的示例。
  • 对话框目的地有关如何创建用于将用户转到对话框的目的地的示例。
  • 导航到目的地有关如何从一个目的地导航到另一个目的地的详细指南。
  • 嵌套图:有关如何将一个导航图嵌套在另一个导航图中的深度指南。