導覽元件提供以 Kotlin 為基礎的網域特定語言 (即 DSL),而且必須依賴 Kotlin 的類型安全建構工具來建立資料結構。這個 API 可讓您在 Kotlin 程式碼中 (而非 XML 資源內) 透過宣告製作圖表。如果您想動態建構應用程式的導覽機制,這項功能就能派上用場。例如,應用程式可從外部網路服務下載及快取導覽設定,然後使用該設定在活動的 onCreate()
函式中動態建構導覽圖。
依附元件
如要使用 Kotlin DSL,請在應用程式的 build.gradle
檔案中新增以下依附元件:
Groovy
dependencies { def nav_version = "2.7.7" api "androidx.navigation:navigation-fragment-ktx:$nav_version" }
Kotlin
dependencies { val nav_version = "2.7.7" api("androidx.navigation:navigation-fragment-ktx:$nav_version") }
建構圖表
首先從基於 Sunflower 應用程式的基本範例著手。在這個範例中,有兩個目的地:home
和 plant_detail
。當使用者首次啟動應用程式時,會顯示 home
目的地。這個目的地會顯示使用者花園中的植物清單。當使用者選取其中一種植物時,應用程式會前往 plant_detail
目的地。
圖 1 顯示了這些目的地和 plant_detail
目的地所需的引數,以及應用程式用於從 home
導覽至 plant_detail
的動作 to_plant_detail
。
![Sunflower 應用程式有兩個目的地,以及一個用於連接這二者的動作。](https://developer.android.com/static/images/guide/navigation/navigation-kotlin-dsl-1.png?authuser=9&hl=zh-tw)
home
和 plant_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 會使用路徑字串而非 ID。每個路徑都用獨特的字串表示,因此最佳做法是將這些路徑定義為常數,以降低因為錯字而發生程式錯誤的風險。
處理引數時,這些常數會構建至路徑字串中。將這個邏輯建構至路徑中,同樣可降低因為錯字而發生程式錯誤的風險。
object nav_routes {
const val home = "home"
const val plant_detail = "plant_detail"
}
object nav_arguments {
const val plant_id = "plant_id"
const val plant_name = "plant_name"
}
使用 NavGraphBuilder DSL 建構圖表
定義常數後,即可開始建構導覽圖。
val navController = findNavController(R.id.nav_host_fragment)
navController.graph = navController.createGraph(
startDestination = nav_routes.home
) {
fragment<HomeFragment>(nav_routes.home) {
label = resources.getString(R.string.home_title)
}
fragment<PlantDetailFragment>("${nav_routes.plant_detail}/{${nav_arguments.plant_id}}") {
label = resources.getString(R.string.plant_detail_title)
argument(nav_arguments.plant_id) {
type = NavType.StringType
}
}
}
在這個範例中,結尾的 lambda 使用 fragment()
DSL 建構工具函式定義了兩個片段目的地。此函式要求目的地的路徑字串,該字串從常數中取得。此函式也接受選用的 lambda 來進行其他設定 (例如目的地標籤),以及用於引數和深層連結的嵌入式建構工具函式。
管理每個目的地 UI 的 Fragment
類別會做為用角括弧 (<>
) 括起來的參數化類型來傳送。這與在使用 XML 定義的片段目的地上設定 android:name
屬性的作用相同。
使用 Kotlin DSL 圖表進行導覽
最後,您可以使用標準 NavController.navigate() 呼叫,從 home
導覽至 plant_detail
:
private fun navigateToPlant(plantId: String) {
findNavController().navigate("${nav_routes.plant_detail}/$plantId")
}
在 PlantDetailFragment
中,您可以取得引數的值,如以下範例所示:
val plantId: String? = arguments?.getString(nav_arguments.plant_id)
如要進一步瞭解如何在導覽時提供引數,請參閱「提供目的地引數」一節。
本指南的其餘部分將說明常見的導覽圖表元素、目的地,以及如何使用這些項目建構圖表。
目的地
Kotlin DSL 針對三種目的地類型提供內建支援,這三種類型為 Fragment
、Activity
和 NavGraph
目的地。每個目的地都有專屬的內嵌擴充功能,可用來建構及設定這個目的地。
片段目的地
fragment()
DSL 函式可經過參數化處理,轉換為實作的片段類別,並使用專屬路徑字串指派給這個目的地,目的地後方會接 lambda 函式,您可在該函式中提供其他設定,如「使用 Kotlin DSL 圖表進行導覽」一節所述。
fragment<FragmentDestination>(nav_routes.route_name) {
label = getString(R.string.fragment_title)
// arguments, deepLinks
}
活動目的地
activity()
DSL 函式使用專屬路徑字串來指派給這個目的地,但不會參數化至任何實作活動類別。您可在結尾的 lambda 中設定選用的 activityClass
。這種靈活性讓您能夠為應使用隱含意圖 (而非明確的活動類別) 啟動的活動定義活動目的地。與片段目的地一樣,您也可以設定標籤、引數和深層連結。
activity(nav_routes.route_name) {
label = getString(R.string.activity_title)
// arguments, deepLinks...
activityClass = ActivityDestination::class
}
導覽圖目的地
navigation()
DSL 函式可用來建構巢狀導覽圖。這個函式使用三個引數:指派給圖表的路徑、圖表的起始目的地路徑,以及用於進一步設定圖表的 lambda。有效的元素包括其他目的地、引數、深層連結,以及目的地的描述性標籤。在使用 NavigationUI 將導覽圖繫結至 UI 元件時,這個標籤將大有用處。
navigation("route_to_this_graph", nav_routes.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
}
提供目的地引數
任何目的地都可以定義選用或必需的引數。您可以使用 NavDestinationBuilder
上的 argument()
函式定義動作,這是所有目的地建構工具類型的基礎類別。這個函式會使用引數名稱做為字串,以及做為用於建構及設定 NavArgument
的 lambda。
在 lambda 中,您可以指定引數資料類型、預設值 (如適用),以及是否可為空值。
fragment<PlantDetailFragment>("${nav_routes.plant_detail}/{${nav_arguments.plant_id}}") {
label = getString(R.string.plant_details_title)
argument(nav_arguments.plant_id) {
type = NavType.StringType
defaultValue = getString(R.string.default_plant_id)
nullable = true // default false
}
}
如果指定了 defaultValue
,系統將可以推論類型。如果同時指定了 defaultValue
和 type
,則類型必須相符。如需可用引數類型的完整清單,請參閱 NavType 參考說明文件。
提供自訂類型
某些類型不支援剖析路徑或深層連結所使用字串中的值,例如 ParcelableType
和 SerializableType
,因為不依賴執行階段的反射。提供自訂的 NavType
類別,就能完全控制從路徑或深層連結剖析類型的方式。這樣,您就能夠使用 Kotlin 序列化或其他程式庫,提供無反射的自訂類型編碼和解碼作業。
舉例來說,將代表搜尋參數的資料類別傳遞至搜尋畫面,就可以同時實作 Serializable
(提供編碼/解碼支援) 和 Parcelize
(支援儲存至 Bundle
和從中還原):
@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>(nav_routes.plant_search) {
label = getString(R.string.plant_search_title)
argument(nav_arguments.search_parameters) {
type = SearchParametersType
defaultValue = SearchParameters("cactus", emptyList())
}
}
NavType
會封裝每個欄位的寫入和讀取作業,
因此當您瀏覽至NavType
以確保格式一致:
val params = SearchParameters("rose", listOf("available"))
val searchArgument = SearchParametersType.serializeAsValue(params)
navController.navigate("${nav_routes.plant_search}/$searchArgument")
您可以從目的地中的引數取得此參數:
val params: SearchParameters? = arguments?.getParcelable(nav_arguments.search_parameters)
深層連結
深層連結可以加入任何目的地,就像使用以 XML 為基礎的連結一樣 導覽圖「為目的地建立深層連結」一文中定義的所有相同程序,皆適用於使用 Kotlin DSL 建立明確深層連結的程序。
不過,在建立隱含深層連結時,您沒有可針對 <deepLink>
元素進行分析的 XML 導覽資源。 因此,將 <nav-graph>
元素放入 AndroidManifest.xml
檔案並不妥,您必須將意圖篩選器手動新增至活動。您提供的意圖篩選器應符合應用程式深層連結的基本網址模式、動作和 MIME 類型。
您可以使用 deepLink()
DSL 函式,為每個深層連結的目的地提供更具體的 deeplink
。此函式接受 NavDeepLink
,其中包含代表 URI 模式的 String
、代表意圖動作的 String
,以及代表 MIME 類型的 String
。
例如:
deepLink {
uriPattern = "http://www.example.com/plants/"
action = "android.intent.action.MY_ACTION"
mimeType = "image/*"
}
可新增的深層連結數量沒有上限。每次呼叫 deepLink()
時,都會有一個新的深層連結附加至由該目的地維護的清單。
較複雜的隱含深層連結情境也會定義路徑和基於查詢的參數,如下所示:
val baseUri = "http://www.example.com/plants"
fragment<PlantDetailFragment>(nav_routes.plant_detail) {
label = getString(R.string.plant_details_title)
deepLink(navDeepLink {
uriPattern = "${baseUri}/{id}"
})
deepLink(navDeepLink {
uriPattern = "${baseUri}/{id}?name={plant_name}"
})
}
您可以使用字串內插類型來簡化定義。
限制
Safe Args 外掛程式與 Kotlin DSL 不相容,因為外掛程式會尋找 XML 資源檔案來產生 Directions
和 Arguments
類別。
瞭解詳情
如要瞭解如何為 Kotlin DSL 和 Navigation Compose 程式碼提供類型安全,請參閱導覽類型安全頁面。