巢狀結構圖

應用程式內的登入流程、精靈或其他子流程通常是巢狀導覽圖的最佳呈現方式。像這樣為獨立的子導覽流程建立巢狀結構,應用程式 UI 的主要流程將更容易理解與管理。

此外,巢狀結構圖還可以重複使用。這些函式也提供封裝層級,巢狀結構圖以外的目的地無法直接存取巢狀圖表內的任何目的地。這些目的地應 navigate() 至巢狀結構圖本身;在這個巢狀結構圖中,內部邏輯可以變更,不會影響其餘部分。

範例

應用程式的「頂級」導覽圖,起始處應設在應用程式啟動時使用者首先看到的目的地,且應包含他們在應用程式中到處瀏覽時所看到的目的地。

圖 1.頂級導覽圖。

以圖 1 的頂級導覽圖為例,假設您只想要求使用者在首次啟動應用程式時查看「title_screen」和「register」畫面。之後,系統會儲存使用者資訊,並且在應用程式的後續版本中,直接將其導向「match」畫面。

最佳做法是將「match」畫面設定為頂級導覽圖的「起始目的地」,然後將標題和註冊畫面移至巢狀結構圖,如圖 1 所示:

圖 2. 頂層導覽圖現在包含巢狀結構圖。

當「match」畫面啟動後,請檢查是否有已註冊的使用者。如果使用者尚未註冊,請前往註冊畫面。

如要進一步瞭解條件式導覽情境,請參閱「條件式導覽」。

Compose

如要使用 Compose 建立巢狀導覽圖,請使用 NavGraphBuilder.navigation() 函式。在圖表中加入目的地時,您會使用 navigation(),就像使用 NavGraphBuilder.composable()NavGraphBuilder.dialog() 函式一樣。

主要差異在於 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 時,您可以使用導覽編輯器建立巢狀圖。步驟如下:

  1. 在導覽編輯器中,按住 Shift 鍵,然後點選要加入巢狀圖表的目的地。
  2. 按一下滑鼠右鍵以開啟內容選單,然後依序選取「Move to Nested Graph」>「New Graph」。這些到達網頁會包含巢狀圖中。圖 2 顯示導覽編輯器中的巢狀圖:

    圖 2.導覽編輯器中的巢狀圖表
  3. 按一下巢狀圖表。「Attributes」面板中會顯示以下屬性:

    • 類型:包含「巢狀圖」
    • 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>

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

其他資源

如要進一步瞭解導航,請參閱下列其他資源。

範例

程式碼研究室

影片