嵌套图

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

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

示例

应用的顶级导航图应从用户启动应用时看到的初始目的地开始,并应包含用户在您的应用中移动时看到的目的地。

图 1.顶级导航图。

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

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

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

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

如需详细了解条件导航场景,请参阅条件导航

Compose

如需使用 Compose 创建嵌套导航图,请使用 NavGraphBuilder.navigation() 函数。在向图表添加目的地时,您可以像使用 NavGraphBuilder.composable()NavGraphBuilder.dialog() 函数一样使用 navigation()

主要区别在于,navigation 会创建嵌套图,而不是新的目的地。然后,在 navigation 的 lambda 中调用 composabledialog,以向嵌套图中添加目的地。

请考虑以下代码段如何使用 Compose 实现图 2 中的图表:

NavHost(navController, startDestination = "title_screen") {
    composable("title_screen") {
        TitleScreen(
            onPlayClicked = { navController.navigate("register") },
            onLeaderboardsClicked = { /* Navigate to leaderboards */ }
        )
    }
    composable("register") {
        RegisterScreen(
            onSignUpComplete = { navController.navigate("gameInProgress") }
        )
    }
    navigation(startDestination = "match", route = "gameInProgress") {
        composable("match") {
            MatchScreen(
                onStartGame = { navController.navigate("in_game") }
            )
        }
        composable("in_game") {
            InGameScreen(
                onGameWin = { navController.navigate("results_winner") },
                onGameLose = { navController.navigate("game_over") }
            )
        }
        composable("results_winner") {
            ResultsWinnerScreen(
                onNextMatchClicked = {
                    navController.navigate("match") {
                        popUpTo("match") { inclusive = true }
                    }
                },
                onLeaderboardsClicked = { /* Navigate to leaderboards */ }
            )
        }
        composable("game_over") {
            GameOverScreen(
                onTryAgainClicked = {
                    navController.navigate("match") {
                        popUpTo("match") { inclusive = true }
                    }
                }
            )
        }
    }
}

如需直接导航到嵌套目的地,请使用 route,方法与前往任何其他目的地一样。这是因为路由是一个全局概念,任何屏幕都可以导航到:

navController.navigate("match")

扩展函数

您可以使用 NavGraphBuilder 上的扩展函数向图添加目的地。您可以将这些扩展函数与预构建的 navigationcomposabledialog 扩展方法一起使用。

例如,您可以使用扩展函数添加上一部分演示的嵌套图:

fun NavGraphBuilder.addNestedGraph(navController: NavController) {
    navigation(startDestination = "match", route = "gameInProgress") {
        composable("match") {
            MatchScreen(
                onStartGame = { navController.navigate("in_game") }
            )
        }
        composable("in_game") {
            InGameScreen(
                onGameWin = { navController.navigate("results_winner") },
                onGameLose = { navController.navigate("game_over") }
            )
        }
        composable("results_winner") {
            ResultsWinnerScreen(
                onNextMatchClicked = { navController.navigate("match") },
                onLeaderboardsClicked = { /* Navigate to leaderboards */ }
            )
        }
        composable("game_over") {
            GameOverScreen(
                onTryAgainClicked = { navController.navigate("match") }
            )
        }
    }
}

然后,您可以在传递给 NavHost 的 lambda 中调用此函数,而不是以内嵌方式调用导航。以下示例对此进行了演示:

@Composable
fun MyApp() {
    val navController = rememberNavController()
    NavHost(navController, startDestination = "title_screen") {
        composable("title_screen") {
            TitleScreen(
                onPlayClicked = { navController.navigate("register") },
                onLeaderboardsClicked = { /* Navigate to leaderboards */ }
            )
        }
        composable("register") {
            RegisterScreen(
                onSignUpComplete = { navController.navigate("gameInProgress") }
            )
        }

        // Add the nested graph using the extension function
        addNestedGraph(navController)
    }
}

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);
    
  7. 返回 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">

    <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>

其他资源

如需详细了解 Navigation,请参阅下面列出的其他资源:

示例

Codelab

视频