设计导航图

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

目的地类型

目的地大致分为三种类型:托管、对话框和 Activity。下表概述了这三种目的地类型及其用途。

类型

说明

用例

托管

填满整个导航宿主。也就是说,托管目的地的大小与导航宿主的大小相同,并且之前的目的地不可见。

主屏幕和详情屏幕。

对话框

显示叠加界面组件。此界面不受导航宿主位置或其大小的限制。之前的目的地在该目的地下方可见。

提醒、选择、表单。

Activity

表示应用中独特的屏幕或功能。

作为导航图的退出点,启动一个新的 Android Activity,该 Activity 独立于 Navigation 组件进行管理。

在现代 Android 开发中,一个应用由单个 Activity 组成。因此,Activity 目的地最适合用于与第三方 Activity 交互或作为 迁移过程 的一部分。

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

框架

尽管相同的通用工作流适用于所有情况,但创建导航宿主和导航图的具体方式取决于您使用的界面框架。

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

Compose

在 Compose 中,使用可序列化对象或类来定义 路由。路由描述了如何到达目的地,并包含目的地所需的所有信息。

使用 @Serializable 注解自动为您的路由类型创建必要的序列化和反序列化方法。此注解由 Kotlin 序列化插件提供。按照这些说明添加此插件

定义好路由后,使用 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) }
}

请注意此代码段中的以下几点:

  • 使用 "John Smith" 作为 name 的参数,Profile 路由指定了导航图中的起始目的地。
  • 目的地本身是 composable<Profile>{} 块。
  • 可组合项 ProfileScreenprofile.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 表达式。

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 管理应用的导航图。这本质上是一个 GUI,您可以使用它来创建和编辑 NavigationFragment XML,如前一节所示。

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

嵌套图

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

进一步阅读

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

  • 概览务必阅读 Navigation 组件的总体概览。
  • Activity 目的地如何实现将用户带到 Activity 的目的地的示例。
  • 对话框目的地如何创建将用户带到对话框的目的地的示例。
  • 导航到目的地一份详细指南,涵盖了如何从一个目的地导航到另一个目的地。
  • 嵌套图一份深入指南,介绍了如何在另一个导航图中嵌套一个导航图。