嵌套图

应用中的登录流程、向导或其他子流程通常是嵌套导航图的最佳表示形式。通过以这种方式嵌套独立的子导航流程,您可以更轻松地理解和管理应用界面的主流程。

此外,嵌套图可以重复使用。它们还提供了一定程度的封装,即嵌套图之外的目的地无法直接访问嵌套图内的任何目的地。相反,这些目的地应 navigate() 到嵌套图本身,在嵌套图中,内部逻辑可以发生更改,而不会影响图表的其余部分。

示例

应用的顶级导航图应从用户启动应用时看到的初始目的地开始,且应包含他们在应用中四处浏览时看到的目的地。

图 1. 顶级导航图。

以图 1 中的顶级导航图为例,假设您希望仅在用户首次启动应用时让其看到 title_screenregister 界面。之后,系统会存储用户信息。用户以后启动应用时,您应直接将其转到匹配界面。

最佳实践应该是将 match 界面设置为顶级导航图的起始目的地,并将标题界面和注册界面移至嵌套图中,如图 1 所示:

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

匹配界面启动后,检查是否有注册用户。如果用户未注册,请将其转到注册界面。

如需详细了解条件导航情形,请参阅条件导航

Compose

如需使用 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 时,您可以使用 Navigation Editor 创建嵌套图。具体步骤如下:

  1. 在 Navigation Editor 中,按住 Shift 键,然后点击您想要添加到嵌套图中的目的地。
  2. 右键点击以打开上下文菜单,然后依次选择 Move to Nested Graph > New Graph。目的地包含在嵌套图中。图 2 显示了 Navigation Editor 中的嵌套图:

    图 2. Navigation Editor 中的嵌套图
  3. 点击嵌套图。此时 Attributes 面板中会显示以下属性:

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

  5. 点击 Text 标签页,以切换到 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. 返回 Design 标签页,然后通过点击 Root 回到根图。

使用 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">      <include app:graph="@navigation/included_graph" />      <fragment         android:id="@+id/fragment"         android:name="com.example.myapplication.BlankFragment"         android:label="Fragment in Root Graph"         tools:layout="@layout/fragment_blank">         <action             android:id="@+id/action_fragment_to_second_graph"             app:destination="@id/second_graph" />     </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"     android:id="@+id/second_graph"     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>