片段和 Kotlin DSL

導覽元件提供以 Kotlin 為基礎的網域專屬語言 (即 DSL),而且必須依賴 Kotlin 的類型安全建構工具來建立資料結構。這個 API 可讓您在 Kotlin 程式碼中透過宣告方式製作圖表,而非 而不是 XML 資源中的如果您想建構應用程式的 以動態方式進行導覽例如,應用程式可從外部網路服務下載及快取導覽設定,然後使用該設定在活動的 onCreate() 函式中動態建構導覽圖。

依附元件

如要搭配使用 Kotlin DSL 與 Fragment,請將以下依附元件新增至應用程式的 build.gradle 檔案:

Groovy

dependencies {
    def nav_version = "2.9.3"

    api "androidx.navigation:navigation-fragment-ktx:$nav_version"
}

Kotlin

dependencies {
    val nav_version = "2.9.3"

    api("androidx.navigation:navigation-fragment-ktx:$nav_version")
}

建構圖表

以下是基於 Sunflower 應用程式的基本範例。在這個範例中,有兩個目的地:homeplant_detail。當使用者首次啟動應用程式時,會顯示 home 目的地。這個目的地會顯示使用者花園中的植物清單。當使用者選取其中一種植物時,應用程式會前往 plant_detail 目的地。

圖 1 顯示了這些目的地和 plant_detail 目的地所需的引數,以及應用程式用於從 home 導覽至 plant_detail 的動作 to_plant_detail

Sunflower 應用程式有兩個目的地,以及一個用於連接這二者的動作。
圖 1. Sunflower 應用程式有兩個目的地:homeplant_detail,以及一個用於連接這二者的動作。

託管 Kotlin DSL 導覽圖

建構應用程式的導覽圖之前,需要一個代管 圖表。這個範例使用了片段,因此會將圖表託管在 FragmentContainerView 內的 NavHostFragment 中:

<!-- activity_garden.xml -->
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true" />

</FrameLayout>

請注意,這個範例中並未設定 app:navGraph 屬性。圖表 並未定義為資源 res/navigation 資料夾,因此必須設為 onCreate() 的一部分 活動中處理程序。

在 XML 中,會有一個動作將目的地 ID 和一個或多個引數連結在一起。不過,使用導覽 DSL 時,路徑可以包含引數,當做路徑的一部分,代表使用 DSL 時沒有動作的概念。

下一步是定義定義圖表時要使用的路徑。

為圖表建立路線

系統會將基於 XML 的導覽圖剖析為一部分 Android 建構程序的各個環節系統會為每個 id 建立數字常數 屬性。這些建構時間產生的靜態 ID 在執行階段建構導覽圖時可使用,以便導覽 DSL 使用 serializable Type,而非 而非客戶 ID每條路線都以不重複的類型表示。

處理引數時,這些常數會建構至路徑類型中。這樣就能確保類型安全 做為導覽引數

@Serializable data object Home
@Serializable data class Plant(val id: String)

定義路徑後,即可建構導覽圖。

val navController = findNavController(R.id.nav_host_fragment)
navController.graph = navController.createGraph(
    startDestination = Home
) {
    fragment<HomeFragment, Home> {
        label = resources.getString(R.string.home_title)
    }
    fragment<PlantDetailFragment, PlantDetail> {
        label = resources.getString(R.string.plant_detail_title)
    }
}

在這個範例中,使用 fragment() DSL 建構工具函式定義了兩個片段目的地。這個函式需要兩個類型引數

首先,Fragment 類別 可為這個目的地提供 UI。設定效果的效果等同於 在定義的片段目的地上設定 android:name 屬性 以 XML 檔案形式提供內容

第二是路徑此類型必須是可序列化的類型,且會從 Any 擴充。這項服務 應包含此目的地會使用的任何導覽引數; 及其類型

此函式也接受選用的 lambda 來進行其他設定 (例如目的地標籤),以及用於自訂引數和深層連結的嵌入式建構工具函式。

最後,您可以使用homeplant_detail NavController.navigate() 通話:

private fun navigateToPlant(plantId: String) {
   findNavController().navigate(route = PlantDetail(id = plantId))
}

PlantDetailFragment 中,您可以取得目前的 NavBackStackEntry,並對其呼叫 toRoute 來取得路徑例項,藉此取得導覽引數。

val plantDetailRoute = findNavController().getBackStackEntry<PlantDetail>().toRoute<PlantDetail>()
val plantId = plantDetailRoute.id

如果 PlantDetailFragment 使用 ViewModel,請使用 SavedStateHandle.toRoute 取得路由例項。

val plantDetailRoute = savedStateHandle.toRoute<PlantDetail>()
val plantId = plantDetailRoute.id

本指南的其餘部分將說明常見的導覽圖表元素、目的地,以及如何使用這些項目建構圖表。

目的地

Kotlin DSL 針對三種目的地類型提供內建支援,這三種類型為 FragmentActivityNavGraph 目的地。每個目的地都有專屬的內嵌擴充功能,可用來建構及設定這個目的地。

片段目的地

fragment() DSL 函式可經過參數化處理,轉換為 UI 的片段類別,以及用於唯一識別此目的地的路徑類型,目的地後方會接 lambda 函式,您可在該函式中提供其他設定,如「使用 Kotlin DSL 圖表進行導覽」一節所述。

fragment<MyFragment, MyRoute> {
   label = getString(R.string.fragment_title)
   // custom argument types, deepLinks
}

活動目的地

activity() DSL 函式會採用路線的 type 參數,但不會參數化為 任何實作活動類別。您可在結尾的 lambda 中設定選用的 activityClass。這種靈活度可讓您針對不同區域 應該使用 意圖 則不符合說明與片段目的地一樣,您也可以設定標籤、自訂引數和深層連結。

activity<MyRoute> {
   label = getString(R.string.activity_title)
   // custom argument types, deepLinks...

   activityClass = MyActivity::class
}

navigation() DSL 函式可用於建立巢狀導覽 圖表。這個函式採用類型 參數,用於指派給這個圖表的路徑。它也使用兩個引數:圖表的起始目的地路徑,以及用於進一步設定圖表的 lambda。有效的元素包括其他目的地、自訂引數類型、深層連結,以及目的地的描述性標籤。在使用 NavigationUI 將導覽圖繫結至 UI 元件時,這個標籤將大有用處。

@Serializable data object HomeGraph
@Serializable data object Home

navigation<HomeGraph>(startDestination = Home) {
   // label, other destinations, deep links
}

支援自訂目的地

如果您使用無法直接支援 Kotlin DSL 的新目的地類型,則可使用 addDestination() 將這些目的地新增至您的 Kotlin DSL:

// The NavigatorProvider is retrieved from the NavController
val customDestination = navigatorProvider[CustomNavigator::class].createDestination().apply {
    route = Graph.CustomDestination.route
}
addDestination(customDestination)

您也可以使用一元加號運算子,將新建構的目的地直接新增至圖表:

// The NavigatorProvider is retrieved from the NavController
+navigatorProvider[CustomNavigator::class].createDestination().apply {
    route = Graph.CustomDestination.route
}

提供目的地引數

目的地引數可定義為路徑類別的一部分。您可以輸入 定義的方法與對任何 Kotlin 類別相同必要引數會定義為非空值類型,選用引數則會定義為預設值。

表示路徑及其引數的基礎機制是字串 基於這個原因。使用字串建立路徑模型可讓系統儲存導覽狀態, 在設定期間從磁碟還原 變更系統啟動的程序 死亡。因此,每個導覽引數都必須可序列化,也就是說,它應具備將引數值的記憶體內表示法轉換為 String 的方法。

@Serializable 註解新增至物件時,Kotlin 序列化外掛程式會自動為基本類型產生序列化方法。

@Serializable
data class MyRoute(
  val id: String,
  val myList: List<Int>,
  val optionalArg: String? = null
)

fragment<MyFragment, MyRoute>

提供自訂類型

針對自訂引數類型,您需要提供自訂 NavType 類別。這可讓您精確控制從路徑或深層連結剖析類型的方式。

例如,用於定義搜尋畫面的路徑可能包含 代表搜尋參數:

@Serializable
data class SearchRoute(val parameters: SearchParameters)

@Serializable
@Parcelize
data class SearchParameters(
  val searchQuery: String,
  val filters: List<String>
)

自訂 NavType 可按以下方式編寫:

val SearchParametersType = object : NavType<SearchParameters>(
  isNullableAllowed = false
) {
  override fun put(bundle: Bundle, key: String, value: SearchParameters) {
    bundle.putParcelable(key, value)
  }
  override fun get(bundle: Bundle, key: String): SearchParameters {
    return bundle.getParcelable(key) as SearchParameters
  }

  override fun serializeAsValue(value: SearchParameters): String {
    // Serialized values must always be Uri encoded
    return Uri.encode(Json.encodeToString(value))
  }

  override fun parseValue(value: String): SearchParameters {
    // Navigation takes care of decoding the string
    // before passing it to parseValue()
    return Json.decodeFromString<SearchParameters>(value)
  }
}

然後,它就可以像其他任何類型一樣在 Kotlin DSL 中使用:

fragment<SearchFragment, SearchRoute>(
    typeMap = mapOf(typeOf<SearchParameters>() to SearchParametersType)
) {
    label = getString(R.string.plant_search_title)
}

前往目的地時,建立路徑的執行個體:

val params = SearchParameters("rose", listOf("available"))
navController.navigate(route = SearchRoute(params))

您可以從目的地中的路徑取得此參數:

val searchRoute = navController().getBackStackEntry<SearchRoute>().toRoute<SearchRoute>()
val params = searchRoute.parameters

深層連結

深層連結可以加入任何目的地,就像使用以 XML 為基礎的連結一樣 導覽圖「為目的地建立深層連結」一文中定義的所有相同程序,皆適用於使用 Kotlin DSL 建立深層連結的程序。

不過,在建立隱含深層連結時,您沒有可針對 <deepLink> 元素進行分析的 XML 導覽資源。因此,您無法將 <nav-graph> 元素放入 AndroidManifest.xml 檔案,而必須手動將意圖篩選器新增至活動。意圖 您提供的篩選器必須符合 追蹤應用程式的深層連結

如要將深層連結新增至目的地,請在該位置呼叫 deepLink 函式 目的地的 lambda。它會將路徑做為參數化類型,以及參數 basePath 做為用於深層連結的網址基準路徑。

您也可以使用 deepLinkBuilder 尾隨 lambda 新增動作和 mimetype。

以下範例會為 Home 目的地建立深層連結 URI。

@Serializable data object Home

fragment<HomeFragment, Home>{
  deepLink<Home>(basePath = "www.example.com/home"){
    // Optionally, specify the action and/or mime type that this destination
    // supports
    action = "android.intent.action.MY_ACTION"
    mimeType = "image/*"
  }
}

URI 格式

系統會根據下列規則,自動使用路徑欄位產生深層連結 URI 格式:

  • 必要參數會以路徑參數的形式附加 (例如:/{id})
  • 含有預設值的參數 (選用參數) 會以查詢參數的形式附加 (例如:?name={name})
  • 產品素材資源集合會以查詢參數的形式附加 (例如:?items={value1}&items={value2})
  • 參數的順序與路徑中欄位的順序相符

例如以下路線類型:

@Serializable data class PlantDetail(
  val id: String,
  val name: String,
  val colors: List<String>,
  val latinName: String? = null,
)

產生的 URI 格式如下:

basePath/{id}/{name}/?colors={color1}&colors={color2}&latinName={latinName}

可新增的深層連結數量沒有上限。每次呼叫 deepLink() 時,都會有一個新的深層連結附加至由該目的地維護的清單。

限制

Safe Args 外掛程式與 Kotlin DSL 不相容,因為外掛程式會尋找 XML 資源檔案來產生 DirectionsArguments 類別。