嵌套图

应用程序中的登录流程、向导或其他子流程通常最好表示为 嵌套导航图。通过以这种方式嵌套自包含的子导航流程,应用程序 UI 的主要流程更容易理解和管理。

此外,嵌套图是可重用的。它们还提供了一定程度的封装——嵌套图外部的目标无法直接访问嵌套图内的任何目标。相反,它们应该 navigate() 到嵌套图本身,内部逻辑可以在不影响图的其余部分的情况下更改。

示例

应用程序的顶级导航图应从用户在启动应用程序时看到的初始目标开始,并应包括他们在应用程序中移动时看到的目标。

图 1. 顶级导航图。

以图 1 中的顶级导航图为例,假设您希望要求用户仅在应用程序首次启动时看到title_screenregister 屏幕。之后,用户信息将被存储,在应用程序的后续启动中,您应该直接将他们带到match 屏幕。

作为最佳实践,将match 屏幕设置为顶级导航图的起始目标,并将标题和注册屏幕移动到嵌套图中,如图 1 所示

图 2. 顶级导航图现在包含一个嵌套图。

当匹配屏幕启动时,检查是否有注册用户。如果用户未注册,请将用户导航到注册屏幕。

有关条件导航场景的更多信息,请参阅 条件导航

撰写

要使用 Compose 创建嵌套导航图,请使用 NavGraphBuilder.navigation() 函数。与向图中添加目标时使用 NavGraphBuilder.composable()NavGraphBuilder.dialog() 函数类似,你也可以使用 navigation()

主要区别在于 navigation 创建的是嵌套图,而不是新的目标。然后,在 navigation() 的 lambda 中调用 composable()dialog() 向嵌套图添加目标。

以下代码片段展示了如何使用 Compose 实现图 2 中的图。

// Routes
@Serializable object Title
@Serializable object Register

// Route for nested graph
@Serializable object Game

// Routes inside nested graph
@Serializable object Match
@Serializable object InGame
@Serializable object ResultsWinner
@Serializable object GameOver

NavHost(navController, startDestination = Title) {
   composable<Title> {
       TitleScreen(
           onPlayClicked = { navController.navigate(route = Register) },
           onLeaderboardsClicked = { /* Navigate to leaderboards */ }
       )
   }
   composable<Register> {
       RegisterScreen(
           onSignUpComplete = { navController.navigate(route = Game) }
       )
   }
   navigation<Game>(startDestination = Match) {
       composable<Match> {
           MatchScreen(
               onStartGame = { navController.navigate(route = InGame) }
           )
       }
       composable<InGame> {
           InGameScreen(
               onGameWin = { navController.navigate(route = ResultsWinner) },
               onGameLose = { navController.navigate(route = GameOver) }
           )
       }
       composable<ResultsWinner> {
           ResultsWinnerScreen(
               onNextMatchClicked = {
                   navController.navigate(route = Match) {
                       popUpTo(route = Match) { inclusive = true }
                   }
               },
               onLeaderboardsClicked = { /* Navigate to leaderboards */ }
           )
       }
       composable<GameOver> {
           GameOverScreen(
               onTryAgainClicked = {
                   navController.navigate(route = Match) {
                       popUpTo(route = Match) { inclusive = true }
                   }
               }
           )
       }
   }
}

要直接导航到嵌套目标,请使用与导航到任何其他目标时相同的路由类型。这是因为路由是全局概念,用于标识任何屏幕都可以导航到的目标。

navController.navigate(route = Match)

XML

使用 XML 时,可以使用导航编辑器创建嵌套图。为此,请执行以下步骤

  1. 在导航编辑器中,按住 Shift 键,然后单击要包含在嵌套图中的目标。
  2. 右键单击以打开上下文菜单,然后选择 移至嵌套图 > 新建图。目标将被封闭在嵌套图中。图 2 显示了 导航编辑器 中的嵌套图

    图 2. 导航编辑器中的嵌套图
  3. 单击嵌套图。以下属性将显示在 属性 面板中

    • 类型,其中包含“嵌套图”
    • ID,其中包含系统分配的嵌套图 ID。此 ID 用于从代码中引用嵌套图。
  4. 双击嵌套图以显示其目标。

  5. 单击 文本 选项卡以切换到 XML 视图。嵌套导航图已添加到图中。此导航图具有自己的 navigation 元素以及自己的 ID 和一个 startDestination 属性,该属性指向嵌套图中的第一个目标。

    <?xml version="1.0" encoding="utf-8"?>
    <navigation xmlns:app="http://schemas.android.com/apk/res-auto"
       xmlns:tools="http://schemas.android.com/tools"
       xmlns:android="http://schemas.android.com/apk/res/android"
       app:startDestination="@id/mainFragment">
       <fragment
           android:id="@+id/mainFragment"
           android:name="com.example.cashdog.cashdog.MainFragment"
           android:label="fragment_main"
           tools:layout="@layout/fragment_main" >
           <action
               android:id="@+id/action_mainFragment_to_sendMoneyGraph"
               app:destination="@id/sendMoneyGraph" />
           <action
               android:id="@+id/action_mainFragment_to_viewBalanceFragment"
               app:destination="@id/viewBalanceFragment" />
       </fragment>
       <fragment
           android:id="@+id/viewBalanceFragment"
           android:name="com.example.cashdog.cashdog.ViewBalanceFragment"
           android:label="fragment_view_balance"
           tools:layout="@layout/fragment_view_balance" />
       <navigation android:id="@+id/sendMoneyGraph" app:startDestination="@id/chooseRecipient">
           <fragment
               android:id="@+id/chooseRecipient"
               android:name="com.example.cashdog.cashdog.ChooseRecipient"
               android:label="fragment_choose_recipient"
               tools:layout="@layout/fragment_choose_recipient">
               <action
                   android:id="@+id/action_chooseRecipient_to_chooseAmountFragment"
                   app:destination="@id/chooseAmountFragment" />
           </fragment>
           <fragment
               android:id="@+id/chooseAmountFragment"
               android:name="com.example.cashdog.cashdog.ChooseAmountFragment"
               android:label="fragment_choose_amount"
               tools:layout="@layout/fragment_choose_amount" />
       </navigation>
    </navigation>
    
  6. 在代码中,传递连接根图与嵌套图的操作的资源 ID。

Kotlin

view.findNavController().navigate(R.id.action_mainFragment_to_sendMoneyGraph)

Java

Navigation.findNavController(view).navigate(R.id.action_mainFragment_to_sendMoneyGraph);
  1. 返回到 设计 选项卡,通过单击 返回到根图。

使用 include 引用其他导航图

模块化图结构的另一种方法是使用父导航图中的 <include> 元素将一个图 包含在另一个图中。这允许包含的图完全定义在单独的模块或项目中,从而最大限度地提高可重用性。

以下代码片段演示了如何使用 <include>

<!-- (root) nav_graph.xml -->
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/nav_graph"
    app:startDestination="@id/fragment">

    <strong><include app:graph="@navigation/included_graph" /></strong>

    <fragment
        android:id="@+id/fragment"
        android:name="com.example.myapplication.BlankFragment"
        android:label="Fragment in Root Graph"
        tools:layout="@layout/fragment_blank">
        <strong><action
            android:id="@+id/action_fragment_to_second_graph"
            app:destination="@id/second_graph" /></strong>
    </fragment>

    ...
</navigation>
<!-- included_graph.xml -->
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    <strong>android:id="@+id/second_graph"</strong>
    app:startDestination="@id/includedStart">

    <fragment
        android:id="@+id/includedStart"
        android:name="com.example.myapplication.IncludedStart"
        android:label="fragment_included_start"
        tools:layout="@layout/fragment_included_start" />
</navigation>

其他资源

要了解有关导航的更多信息,请参阅以下其他资源。

示例

Codelabs

视频