คอมโพเนนต์การนำทางมีภาษาเฉพาะโดเมนตาม Kotlin หรือ
DSL ที่ใช้ Kotlin
type-safe Builders
API นี้ช่วยให้คุณเขียนกราฟในโค้ด Kotlin ได้อย่างชัดเจน
ไม่ใช่ภายในทรัพยากร XML ซึ่งอาจเป็นประโยชน์หากคุณต้องการสร้าง
การนำทางของแอปแบบไดนามิก เช่น แอปอาจดาวน์โหลดและ
แคชการกำหนดค่าการนำทางจากบริการเว็บภายนอกแล้วใช้
การกำหนดค่านั้นเพื่อสร้างกราฟการนำทางแบบไดนามิกใน
onCreate()
การขึ้นต่อกัน
หากต้องการใช้ Kotlin DSL ให้เพิ่มทรัพยากร Dependency ต่อไปนี้ลงใน
build.gradle
ไฟล์:
ดึงดูด
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 สำหรับกรณีนี้
ตัวอย่างเช่น เรามีปลายทาง 2 แห่ง ได้แก่ home
และ plant_detail
home
ปลายทาง จะแสดงเมื่อผู้ใช้เปิดแอปเป็นครั้งแรก จุดหมายนี้
แสดงรายการต้นไม้จากสวนของผู้ใช้ เมื่อผู้ใช้เลือกหนึ่งใน
ต้นไม้ แอปจะนำทางไปยังจุดหมาย plant_detail
รูปที่ 1 แสดงปลายทางเหล่านี้พร้อมกับอาร์กิวเมนต์ที่แอตทริบิวต์
ปลายทาง plant_detail
และการดำเนินการ to_plant_detail
ที่แอปใช้
เพื่อนำทางจาก home
ไป plant_detail
![แอป Sunflower มีจุดหมาย 2 แห่ง รวมถึงการดำเนินการที่
เพื่อเชื่อมโยงสิ่งต่างๆ เข้าด้วยกัน](https://developer.android.com/static/images/guide/navigation/navigation-kotlin-dsl-1.png?authuser=8&hl=th)
home
และ plant_detail
รวมถึงการดำเนินการที่
เพื่อเชื่อมโยงสิ่งต่างๆ เข้าด้วยกันโฮสติ้ง Kotlin DSL Nav Graph
คุณต้องมีพื้นที่สำหรับโฮสต์ก่อนจึงจะสร้างกราฟการนำทางของแอปได้
กราฟ ตัวอย่างนี้ใช้ Fragment จึงโฮสต์กราฟใน
NavHostFragment
ภายใน
FragmentContainerView
:
<!-- 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 การทำงานจะเชื่อมโยงรหัสปลายทางเข้ากับอาร์กิวเมนต์อย่างน้อย 1 รายการ แต่เมื่อใช้ DSL การนำทาง เส้นทางอาจมีอาร์กิวเมนต์เป็นส่วนหนึ่ง ของเส้นทาง ซึ่งหมายความว่าจะไม่มีแนวคิดเกี่ยวกับการดำเนินการเมื่อใช้ DSL
ขั้นตอนถัดไปคือการกำหนดค่าคงที่ที่คุณจะใช้ในการกำหนด กราฟของคุณ
สร้างค่าคงที่สำหรับกราฟของคุณ
กราฟการนำทางแบบ XML
ถูกแยกวิเคราะห์เป็นส่วนหนึ่งของกระบวนการบิลด์ของ Android มีการสร้างค่าคงที่ตัวเลข
สำหรับแอตทริบิวต์ id
แต่ละรายการที่กำหนดไว้ในกราฟ เวลาบิลด์เหล่านี้จะสร้างแบบคงที่
รหัสจะใช้ไม่ได้เมื่อสร้างกราฟการนำทางขณะรันไทม์ ดังนั้น
DSL การนำทางใช้สตริงเส้นทางแทนรหัส แต่ละเส้นทางจะแสดงด้วย
สตริงที่ไม่ซ้ำกันและเป็นแนวทางปฏิบัติที่ดีที่จะกำหนดค่าเหล่านี้เป็นค่าคงที่เพื่อลด
ความเสี่ยงที่จะเกิดข้อบกพร่อง
เกี่ยวกับการพิมพ์ผิด
เมื่อจัดการกับอาร์กิวเมนต์ ปัจจัยเหล่านี้จะ อยู่ในสตริงเส้นทาง การสร้างตรรกะนี้ลงในเส้นทางจะลดความเสี่ยงของ ข้อบกพร่องเกี่ยวกับการพิมพ์ผิดที่ถาโถมเข้ามา
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 2 รายการโดยใช้
fragment()
ฟังก์ชันเครื่องมือสร้าง DSL ฟังก์ชันนี้ต้องใช้สตริงเส้นทางสำหรับปลายทาง
ซึ่งได้จากค่าคงที่ ฟังก์ชันนี้ยังยอมรับตัวเลือก
lambda เพื่อกำหนดค่าเพิ่มเติม เช่น ป้ายกำกับปลายทาง รวมถึง
ฟังก์ชันเครื่องมือสร้างแบบฝังสำหรับอาร์กิวเมนต์และ Deep Link
ชั้นเรียน Fragment
ที่
จัดการ UI ของแต่ละปลายทางที่ส่งผ่านเป็นประเภทพารามิเตอร์ภายใน
วงเล็บสามเหลี่ยม (<>
) ซึ่งจะมีผลเหมือนกับการตั้งค่า android:name
ในปลายทางส่วนย่อยที่กำหนดไว้โดยใช้ XML
การนำทางด้วยกราฟ DSL ของ Kotlin
สุดท้าย คุณสามารถนำทางจาก home
ไป plant_detail
โดยใช้มาตรฐาน
NavController.navigate()
การโทร:
private fun navigateToPlant(plantId: String) {
findNavController().navigate("${nav_routes.plant_detail}/$plantId")
}
ใน PlantDetailFragment
คุณจะหาค่าของอาร์กิวเมนต์ตามที่แสดงไว้ได้
ในตัวอย่างต่อไปนี้
val plantId: String? = arguments?.getString(nav_arguments.plant_id)
ดูรายละเอียดเกี่ยวกับวิธีระบุอาร์กิวเมนต์เมื่อไปยังส่วนต่างๆ ได้ใน การให้อาร์กิวเมนต์ปลายทาง
ส่วนที่เหลือของคู่มือนี้จะอธิบายองค์ประกอบกราฟการไปยังส่วนต่างๆ, ปลายทาง, และวิธีใช้เมื่อสร้างกราฟ
จุดหมาย
Kotlin DSL รองรับปลายทาง 3 ประเภทในตัว ดังนี้
ปลายทาง Fragment
, Activity
และ NavGraph
รายการมีปลายทางของตัวเอง
ฟังก์ชันส่วนขยายในบรรทัดที่ใช้ได้ในการสร้างและกำหนดค่า
ปลายทาง
ปลายทางของ Fragment
fragment()
ฟังก์ชัน DSL สามารถแปลงเป็นพารามิเตอร์ให้กับ Fragment Class ที่นำไปใช้และนำ
สตริงเส้นทางที่ไม่ซ้ำกันที่จะกำหนดให้ปลายทางนี้ ตามด้วย lambda ซึ่ง
คุณสามารถระบุการกำหนดค่าเพิ่มเติมตามที่อธิบายไว้ใน
การไปยังส่วนต่างๆ ด้วยกราฟ DSL ของ Kotlin
fragment<FragmentDestination>(nav_routes.route_name) {
label = getString(R.string.fragment_title)
// arguments, deepLinks
}
จุดหมายของกิจกรรม
activity()
ฟังก์ชัน DSL ใช้สตริงเส้นทางที่ไม่ซ้ำกันเพื่อกำหนดให้ปลายทางนี้ แต่
ไม่ได้ทำพารามิเตอร์ให้กับคลาสกิจกรรมการติดตั้งใช้งานใดๆ แต่คุณตั้งค่า
ตัวเลือก activityClass
ใน lambda ต่อท้าย ความยืดหยุ่นนี้ทำให้คุณสามารถ
กำหนดปลายทางกิจกรรมสำหรับกิจกรรมที่ควรเปิดตัวโดยใช้
Intent แบบไม่เจาะจงปลายทาง โดยที่
คลาสกิจกรรมที่ชัดเจน
นั้นฟังดูไม่สมเหตุสมผล เช่นเดียวกับปลายทางของส่วนย่อย
นอกจากนี้คุณยังกำหนดค่าป้ายกำกับ อาร์กิวเมนต์ และ Deep Link ได้ด้วย
activity(nav_routes.route_name) {
label = getString(R.string.activity_title)
// arguments, deepLinks...
activityClass = ActivityDestination::class
}
ปลายทางของกราฟการนำทาง
navigation()
สามารถใช้ฟังก์ชัน DSL เพื่อสร้าง
กราฟการนำทางที่ซ้อนกัน
ฟังก์ชันนี้ใช้อาร์กิวเมนต์ 3 ตัว ได้แก่ เส้นทางไปยัง
กำหนดให้กับกราฟ เส้นทางของปลายทางเริ่มต้นของกราฟ และ
lambda เพื่อกำหนดค่ากราฟเพิ่มเติม องค์ประกอบที่ถูกต้องรวมถึงปลายทางอื่นๆ
อาร์กิวเมนต์, Deep Link และ
ป้ายกำกับที่สื่อความหมายสำหรับปลายทาง
ป้ายกำกับนี้อาจเป็นประโยชน์ในการเชื่อมโยงกราฟการนำทางกับ UI
คอมโพเนนต์ที่ใช้
UI การนำทาง
navigation("route_to_this_graph", nav_routes.home) {
// label, other destinations, deep links
}
การรองรับปลายทางที่กำหนดเอง
หากคุณใช้
ประเภทปลายทางใหม่ที่ไม่
รองรับ Kotlin DSL โดยตรง คุณเพิ่มปลายทางเหล่านี้ลงใน Kotlin ได้
DSL ที่ใช้ addDestination()
// The NavigatorProvider is retrieved from the NavController
val customDestination = navigatorProvider[CustomNavigator::class].createDestination().apply {
route = Graph.CustomDestination.route
}
addDestination(customDestination)
หรืออีกวิธีหนึ่ง คุณสามารถใช้โอเปอเรเตอร์เครื่องหมายบวก (Unary Plus) เพื่อเพิ่ม ปลายทางที่สร้างขึ้นโดยตรงลงในกราฟ
// The NavigatorProvider is retrieved from the NavController
+navigatorProvider[CustomNavigator::class].createDestination().apply {
route = Graph.CustomDestination.route
}
การระบุอาร์กิวเมนต์ปลายทาง
ปลายทางใดก็ตามจะกำหนดอาร์กิวเมนต์ที่ไม่บังคับหรือจำเป็นได้ การทำงาน
สามารถกำหนดได้โดยใช้
argument()
ใน NavDestinationBuilder
ซึ่งเป็นคลาสพื้นฐานสำหรับ
ประเภทเครื่องมือสร้างปลายทาง ฟังก์ชันนี้ใช้ชื่อของอาร์กิวเมนต์เป็นสตริง
และ lambda ที่ใช้สร้างและกำหนดค่า
NavArgument
ใน 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
,
ไม่รองรับการแยกวิเคราะห์ค่าจากสตริงที่เส้นทางหรือ Deep Link ใช้
เนื่องจากไม่ได้อาศัยการสะท้อนขณะรันไทม์ ด้วยการระบุช่องที่กำหนดเอง
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)
Deep Link
คุณเพิ่ม Deep Link ไปยังปลายทางใดก็ได้ เช่นเดียวกับที่เพิ่ม Deep Link ด้วยการใช้ XML กราฟการนำทาง ขั้นตอนเดียวกันทั้งหมดที่กำหนดไว้ใน การสร้าง Deep Link สำหรับปลายทาง นำไปใช้กับกระบวนการสร้าง Explicit Deep Link โดยใช้ Kotlin DSL
เมื่อสร้าง Implicit Deep Link
แต่คุณไม่มีทรัพยากรการนำทาง XML ที่สามารถวิเคราะห์
<deepLink>
องค์ประกอบ ดังนั้น คุณจึงไม่สามารถวาง <nav-graph>
ในไฟล์ AndroidManifest.xml
และจะต้องเพิ่ม
ตัวกรองความตั้งใจของกิจกรรมด้วยตนเอง
ตัวกรอง Intent ที่คุณระบุควรตรงกับรูปแบบ URL พื้นฐาน การกระทำ และ
ประเภท MIME ของ Deep Link ของแอป
คุณสามารถระบุ deeplink
ที่เจาะจงมากขึ้นสําหรับ Deep Link แต่ละรายการ
ปลายทางโดยใช้
deepLink()
ฟังก์ชัน DSL ฟังก์ชันนี้ยอมรับ NavDeepLink
ที่มี String
แสดงรูปแบบ URI, String
แสดงการดำเนินการของ Intent และ
String
แสดง mimeType
เช่น
deepLink {
uriPattern = "http://www.example.com/plants/"
action = "android.intent.action.MY_ACTION"
mimeType = "image/*"
}
คุณเพิ่ม Deep Link ได้ไม่จำกัดจำนวน ทุกครั้งที่คุณโทร
deepLink()
Deep Link ใหม่จะต่อท้ายรายการที่เก็บไว้สำหรับปลายทางนั้น
สถานการณ์ Deep Link โดยนัยที่ซับซ้อนมากขึ้นซึ่งยังกำหนดเส้นทางและ พารามิเตอร์ตามข้อความค้นหาแสดงอยู่ด้านล่าง
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 และ โค้ดเขียนการนำทาง