瀏覽到目的地

使用 NavController,也就是在 NavHost 中管理應用程式瀏覽的物件,瀏覽到目的地。每個 NavHost 都有其對應的 NavControllerNavController 提供幾種瀏覽至目的地的不同方式,詳情請參閱以下各章節。

若要為片段、活動或檢視畫面擷取 NavController,請使用下列任一方法:

Kotlin:

Java:

擷取 NavController 後,您可以呼叫 navigate() 的其中一個超載功能,以便在目的地之間瀏覽。如以下各節所述,每個超載可支援各種瀏覽情境。

透過安全輸入功能以 Safe Args 進行瀏覽

我們建議您使用 Safe Args Gradle 外掛程式在目的地之間瀏覽。此外掛程式會產生簡單的物件和建構工具類別,方便您在目的地之間進行安全瀏覽。我們建議您使用 Safe Args,以便瀏覽及在目的地之間傳送資料

如要在專案中新增 Safe Args,請在頂層 build.gradle 檔案中納入下列 classpath

Groovy

buildscript {
    repositories {
        google()
    }
    dependencies {
        def nav_version = "2.5.3"
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
    }
}

Kotlin

buildscript {
    repositories {
        google()
    }
    dependencies {
        val nav_version = "2.5.3"
        classpath("androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version")
    }
}

您也必須從兩個可用的外掛程式之中選擇一項套用。

如要產生適合 Java 或混用 Java 及 Kotlin 模組的 Java 語言程式碼,請將以下這一行新增至應用程式或模組build.gradle 檔案:

Groovy

plugins {
  id 'androidx.navigation.safeargs'
}

Kotlin

plugins {
    id("androidx.navigation.safeargs")
}

或者,如要產生適合 Kotlin 模組的 Kotlin 程式碼,請加入:

Groovy

plugins {
  id 'androidx.navigation.safeargs.kotlin'
}

Kotlin

plugins {
    id("androidx.navigation.safeargs.kotlin")
}

根據遷移至 AndroidX 一文,gradle.properties 檔案中必須含有 android.useAndroidX=true

啟用 Safe Args 之後,產生的程式碼會包含您定義的每項操作的類別和方法,以及對應至各個傳送和接收目的地的類別。

Safe Args 會為每個動作產生的目的地產生類別。產生的類別名稱會在原始目的地的類別名稱中加入「Directions」(路線)。舉例來說,如果原始目的地的名稱為 SpecifyAmountFragment,則產生的類別名稱為 SpecifyAmountFragmentDirections

產生的類別包含原始目的地中定義的每種操作的靜態方法。此方法會將任何已定義的操作參數做為引數並傳回 NavDirections 物件,您可以直接將其傳遞到 navigate()

Safe Args 範例

舉例來說,假設有一個瀏覽路線圖,其中包含一項可連接兩個目的地(SpecifyAmountFragmentConfirmationFragment)的操作。ConfirmationFragment 會讀取您提供的單一 float 參數作為操作的一部分。

Safe Args 會產生具有單一方法 actionSpecifyAmountFragmentToConfirmationFragment()SpecifyAmountFragmentDirections 類別,以及名為 ActionSpecifyAmountFragmentToConfirmationFragment 的內部類別。該內部類別來自於 NavDirections,其中儲存了相關的操作 ID 及 float 參數。接著,系統會將返回的 NavDirections 物件直接傳送到 navigate(),如以下範例所示:

Kotlin

override fun onClick(v: View) {
    val amount: Float = ...
    val action =
        SpecifyAmountFragmentDirections
            .actionSpecifyAmountFragmentToConfirmationFragment(amount)
    v.findNavController().navigate(action)
}

Java

@Override
public void onClick(View view) {
    float amount = ...;
    action =
        SpecifyAmountFragmentDirections
            .actionSpecifyAmountFragmentToConfirmationFragment(amount);
    Navigation.findNavController(view).navigate(action);
}

如要進一步瞭解如何透過 Safe Args 在目的地之間傳送資料,請參閱使用 Safe Args 傳送安全輸入的資料

使用 ID 瀏覽

navigate(int) 會使用一項操作或一個目的地的資源 ID。下列程式碼片段說明如何瀏覽至 ViewTransactionsFragment

Kotlin

viewTransactionsButton.setOnClickListener { view ->
   view.findNavController().navigate(R.id.viewTransactionsAction)
}

Java

viewTransactionsButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        Navigation.findNavController(view).navigate(R.id.viewTransactionsAction);
    }
});

如果是按鈕,Navigation.createNavigateOnClickListener() 則有三種變異類型。如果您使用的是 Java 程式設計語言,這些變異非常實用。如果您使用的是 Kotlin,OnClickListener 是一個 SAM 介面,您可以使用追蹤 lambda。相較於直接呼叫 createNavigateOnClickListener(),此方法更簡潔,更容易讀取。

若要處理其他常見的 UI 元件(例如頂端應用程式列及底部瀏覽),請參閱透過 NavigationUI 更新 UI 元件

在瀏覽圖中定義操作時,瀏覽功能會產生對應的 NavAction 類別,其中包含為該操作定義的設定,包括以下:

  • 目的地:目標目的地的資源 ID。
  • 預設引數:(如果有提供)具有目標目的地預設值的 android.os.Bundle
  • 瀏覽選項:瀏覽選項,以 NavOptions 表示。此類別包含所有在目標目的地來回轉換的特殊設定,包括動畫資源設定、彈出式行為,以及是否應以單一頂層模式啟動目的地。

讓我們來看看具有兩個螢幕的圖表範例,以及在螢幕之間瀏覽的操作:

<?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/a">

    <fragment android:id="@+id/a"
              android:name="com.example.myapplication.FragmentA"
              android:label="a"
              tools:layout="@layout/a">
        <action android:id="@+id/action_a_to_b"
                app:destination="@id/b"
                app:enterAnim="@anim/nav_default_enter_anim"
                app:exitAnim="@anim/nav_default_exit_anim"
                app:popEnterAnim="@anim/nav_default_pop_enter_anim"
                app:popExitAnim="@anim/nav_default_pop_exit_anim"/>
    </fragment>

    <fragment android:id="@+id/b"
              android:name="com.example.myapplication.FragmentB"
              android:label="b"
              tools:layout="@layout/b">
        <action android:id="@+id/action_b_to_a"
                app:destination="@id/a"
                app:enterAnim="@anim/nav_default_enter_anim"
                app:exitAnim="@anim/nav_default_exit_anim"
                app:popEnterAnim="@anim/nav_default_pop_enter_anim"
                app:popExitAnim="@anim/nav_default_pop_exit_anim"
                app:popUpTo="@+id/a"
                app:popUpToInclusive="true"/>
    </fragment>
</navigation>

加載瀏覽圖時,系統會剖析這些操作,並透過圖中定義的設定產生對應的 NavAction 物件。舉例來說,action_b_to_a 定義為從目的地 b 導航至目的地 a。其中的操作包含動畫和 popTo 行為,該行為會從返回堆疊中移除所有目的地。所有設定都會擷取為 NavOptions,並附加至 NavAction

如要追蹤此 NavAction,請使用 NavController.navigate() 傳送此操作的 ID,如以下範例所示:

Kotlin

findNavController().navigate(R.id.action_b_to_a)

Java

NavigationHostFragment.findNavController(this).navigate(R.id.action_b_to_a);

透過程式輔助套用 NavOptions

先前的範例說明如何在瀏覽圖的 XML 中指定 NavOptions。但是,特定選項可能會因建構時的未知限制而有所不同。在這種情況下,必須透過程式輔助的方式建立及設定 NavOptions,如以下範例所示:

Kotlin

findNavController().navigate(
    R.id.action_fragmentOne_to_fragmentTwo,
    null,
    navOptions { // Use the Kotlin DSL for building NavOptions
        anim {
            enter = android.R.animator.fade_in
            exit = android.R.animator.fade_out
        }
    }
)

Java

NavController navController = NavHostFragment.findNavController(this);
navController.navigate(
        R.id.action_fragmentOne_to_fragmentTwo,
        null,
        new NavOptions.Builder()
                .setEnterAnim(android.R.animator.fade_in)
                .setExitAnim(android.R.animator.fade_out)
                .build()
);

這個範例使用了 navigate() 的擴充形式,並包含額外的 BundleNavOptions 引數。navigate() 的所有變異類型都有可接受 NavOptions 引數的擴充版本。

也可以在瀏覽至隱式深層連結時,透過程式輔助方式套用 NavOptions

Kotlin

findNavController().navigate(
    deepLinkUri,
    navOptions { // Use the Kotlin DSL for building NavOptions
        anim {
            enter = android.R.animator.fade_in
            exit = android.R.animator.fade_out
        }
    }
)

Java

NavController navController = NavHostFragment.findNavController(this);
navController.navigate(
        deepLinkUri,
        new NavOptions.Builder()
                .setEnterAnim(android.R.animator.fade_in)
                .setExitAnim(android.R.animator.fade_out)
                .build()
);

navigate() 變數會將 Uri 用於隱式深層連結,NavOptions 的執行個體亦然。

使用 DeepLinkRequest 瀏覽

您可以使用 navigate(NavDeepLinkRequest) 直接前往 隱式深層連結目的地,如以下範例所示:

Kotlin

val request = NavDeepLinkRequest.Builder
    .fromUri("android-app://androidx.navigation.app/profile".toUri())
    .build()
findNavController().navigate(request)

Java

NavDeepLinkRequest request = NavDeepLinkRequest.Builder
    .fromUri(Uri.parse("android-app://androidx.navigation.app/profile"))
    .build()
NavHostFragment.findNavController(this).navigate(request)

除了 Uri 以外,NavDeepLinkRequest 也支援含有操作及 MIME 類型的深層連結。如要在要求中新增操作,請使用 fromAction()setAction()。如要在要求中加入 MIME 類型,請使用 fromMimeType()setMimeType()

為了讓 NavDeepLinkRequest 正確比對隱含深層連結目的地,URI、操作及 MIME 類型都必須與目的地中的 NavDeepLink 相符。URI 必須與模式相符、操作須完全相符,而 MIME 類型必須具有關聯性(例如「image/jpg」與「image/*」相符)。

與使用操作或目的地 ID 進行導航不同的是,無論目的地是否可見,您可以透過圖表瀏覽至任何深層連結。您可以在目前的圖表上瀏覽至目的地,也可以是完全不同圖表上的目的地。

使用 NavDeepLinkRequest 瀏覽時,系統不會將返回堆疊重設。這個行為與其他深層連結瀏覽不同,後者會在瀏覽時,將返回堆疊置換。popUpTopopUpToInclusive 仍會從返回堆疊中將目的地移除,就如同您使用 ID 瀏覽一樣。

瀏覽與返回堆疊

Android 會保留一個返回堆疊,其中具有您造訪過的目的地。應用程式的第一個目的地會於使用者開啟應用程式時開啟。每次呼叫 navigate() 方法時,系統會將其他目的地置於堆疊頂端。輕觸向上返回 會分別呼叫 NavController.navigateUp()NavController.popBackStack() 方法,並從堆疊中移除(或彈出)最頂端的目的地。

NavController.popBackStack() 會傳回布林值,表示是否已成功送回其他目的地。傳回 false 最常見的情況,就是手動彈出圖表的起始目的地。

當方法傳回 false 時,NavController.getCurrentDestination() 會傳回 null。您必須負責瀏覽至新的目的地,或是在活動中呼叫 finish() 來處理彈出,如以下範例所示:

Kotlin

...

if (!navController.popBackStack()) {
    // Call finish() on your Activity
    finish()
}

Java

...

if (!navController.popBackStack()) {
    // Call finish() on your Activity
    finish();
}

對話方塊目的地會執行 FloatingWindow 介面,表示其會重疊在返回堆疊中的其他目的地之上。因此,一或多個 FloatingWindow 目的地只能出現在導航返回堆疊的頂端。前往未執行 FloatingWindow 的目的地時,系統會自動從堆疊頂端關閉所有 FloatingWindow 目的地。這可確保目前的目的地絕對會顯示在返回堆疊的其他目的地上方。

舉例來說,如果返回堆疊僅由非浮動的目的地組成,而使用者瀏覽至 Dialog 目的地,那麼返回堆疊可能會類似於圖 1:

頂端為一個對話方塊目的地的返回堆疊
圖 1. 頂端有 Dialog 目的地的返回堆疊。

如果使用者接著瀏覽至其他 Dialog 目的地,就會新增至返回堆疊頂端,如圖 2 所示:

頂端具有兩個對話方塊目的地的返回堆疊
圖 2. 頂端有兩個 Dialog 目的地的返回堆疊。

如果使用者接著瀏覽至非浮動的目的地,任何 FloatingWindow 目的地會先從返回堆疊的頂端彈出,再前往新的目的地,如圖 3 所示:

彈出對話方塊目的地,接著加入新的目的地
圖 3. 系統會彈出 Dialog 目的地,接著加入新的目的地。

popUpTo 及 popUpToInclusive

使用操作進行瀏覽時,可以選擇從返回堆疊外關閉其他目的地。舉例來說,如果您的應用程式有起始的登入流程,當使用者登入時,請將所有與登入相關的目的地都從返回堆疊中彈出,讓「返回」按鈕不會將使用者導回登入流程。

若要從一個目的地前往另一個目的地時將目的地彈出,請在相關的 <action> 元素中加入 app:popUpTo 屬性。app:popUpTo 會告知瀏覽程式庫,在呼叫 navigate() 時,從返回堆疊中關閉某些目的地。屬性的值是最近期應保留在堆疊中的目的地 ID。

您也可以加入 app:popUpToInclusive="true",以表示 app:popUpTo 中指定的目的地也必須從返回堆疊中移除。

popUpTo 範例:循環邏輯

假設應用程式有 A、B 和 C 三個目的地,採用由 A 至 B、B 至 C 及 C 返回至 A 的操作。對應的瀏覽圖如圖 4 所示:

圖 4. 具有三個目的地的循環瀏覽圖:A、B 和 C。

透過一項瀏覽操作,一個目的地會加至返回堆疊中。如果您要依此流程重複瀏覽,返回堆疊就會具有每個目的地的多組集合(A、B、C、A、B、C、A 等)。為避免如此重複的情形,可以在從目的地 C 到目的地 A 的操作中指定 app:popUpToapp:popUpToInclusive,如以下範例所示:

<fragment
    android:id="@+id/c"
    android:name="com.example.myapplication.C"
    android:label="fragment_c"
    tools:layout="@layout/fragment_c">

    <action
        android:id="@+id/action_c_to_a"
        app:destination="@id/a"
        app:popUpTo="@+id/a"
        app:popUpToInclusive="true"/>
</fragment>

抵達目的地 C 之後,返回堆疊會包含每個目的地 (A、B、C) 的一個執行個體。當瀏覽回目的地 A 時,我們也 popUpTo A,亦即在瀏覽時從堆疊中移除了 B 和 C。透過 app:popUpToInclusive="true",我們也從堆疊中將第一個 A 關閉,有效地將其清除。請注意,如果不使用 app:popUpToInclusive,則返回堆疊中會包含兩個目的地 A 的執行個體。

popUpToSaveState 及 restoreSaveState

使用 app:popUpTo 瀏覽至目的地時,Navigation 2.4.0-alpha01 或更高的版本可讓您選擇性地儲存所有從返回堆疊中關閉的目的地的狀態。若要使用這個選項,請在相關的 <action> 元素中加入 app:popUpToSaveState="true"

<action
  android:id="@+id/action_c_to_a"
  app:destination="@id/a"
  app:popUpTo="@+id/a"
  app:popUpToInclusive="true"
  app:popUpToSaveState="true"/>

當您前往目的地時,也可加入 app:restoreSaveState="true" 自動還原 app:destination 中與目的地相關的狀態。