巢狀結構圖

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

此外,巢狀圖還可以重複使用。這類圖表也提供一定程度的封裝,也就是說,巢狀圖以外的目的地無法直接存取圖表中的任何目的地。這些目的地應 navigate() 至巢狀圖本身,這張圖表的內部邏輯可以變更,不會影響其餘部分。

範例

就應用程式的「頂層」導覽圖而言,應該將起始處設在應用程式啟動時首先向使用者顯示的目的地,而且應包含他們在應用程式中到處瀏覽時看到的目的地。

圖 1:頂層導覽圖。

以圖 1 的頂層導覽圖為例,假設您只想要求使用者在首次啟動應用程式時查看 title_screenregister 畫面。之後,系統會儲存使用者資訊,而當應用程式後續啟動時,應該直接將使用者導向「match」畫面。

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

圖 2:包含巢狀圖的頂層導覽圖。

當「match」(相符) 畫面啟動後,請查看是否有註冊的使用者,如果使用者尚未註冊,請將他們導向註冊畫面。

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

Compose

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

主要差異在於 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 編輯器建立巢狀圖,步驟如下:

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

    圖 3:Navigation 編輯器中的巢狀圖。
  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);
  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">

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

其他資源

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

程式碼研究室

影片