Thành phần Điều hướng cung cấp một ngôn ngữ đặc thù theo miền dựa trên Kotlin, hoặc
DSL, dựa trên tiêu chuẩn an toàn về kiểu của Kotlin
trình tạo
của Google. API này cho phép bạn khai báo soạn biểu đồ trong mã Kotlin, thay vì
hơn là bên trong tài nguyên XML. Điều này có thể hữu ích nếu bạn muốn tạo
điều hướng một cách linh động. Ví dụ: ứng dụng của bạn có thể tải xuống và lưu một
từ một dịch vụ web bên ngoài rồi sử dụng cấu hình đó
để tự động tạo biểu đồ điều hướng trong
Hàm onCreate()
.
Phần phụ thuộc
Để sử dụng Kotlin DSL với các Mảnh, hãy thêm phần phụ thuộc sau vào phần phụ thuộc của ứng dụng
Tệp build.gradle
:
Groovy
dependencies { def nav_version = "2.8.4" api "androidx.navigation:navigation-fragment-ktx:$nav_version" }
Kotlin
dependencies { val nav_version = "2.8.4" api("androidx.navigation:navigation-fragment-ktx:$nav_version") }
Tạo biểu đồ
Đây là một ví dụ cơ bản dựa trên thuộc tính Sunflower
ứng dụng. Để làm việc này
Ví dụ: chúng ta có 2 đích đến: home
và plant_detail
. Đích đến home
xuất hiện khi người dùng khởi chạy ứng dụng lần đầu. Đích đến này sẽ hiển thị danh sách các cây trồng trong vườn của người dùng. Khi người dùng chọn một trong các cây trồng này, ứng dụng sẽ điều hướng đến đích đến plant_detail
.
Hình 1 cho thấy những đích đến này cùng với các đối số cần có cho đích đến plant_detail
và một thao tác to_plant_detail
được ứng dụng sử dụng để di chuyển từ home
đến plant_detail
.
Lưu trữ biểu đồ điều hướng DSL Kotlin
Trước khi tạo biểu đồ điều hướng của ứng dụng, bạn cần một nơi để lưu trữ
biểu đồ. Ví dụ bên dưới sẽ sử dụng các mảnh (fragment), do vậy biểu đồ sẽ được lưu trữ trong NavHostFragment
ở bên trongFragmentContainerView
:
<!-- 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>
Lưu ý rằng thuộc tính app:navGraph
không được thiết lập trong ví dụ này. Biểu đồ
không được định nghĩa là tài nguyên trong
thư mục res/navigation
. Do đó, bạn cần đặt thư mục này trong onCreate()
trong hoạt động.
Trong XML, một thao tác liên kết mã nhận dạng của một đích đến chứa một hoặc nhiều đối số. Tuy nhiên, khi sử dụng DSL điều hướng, một tuyến có thể chứa các đối số như một phần của tuyến đường đó. Điều này có nghĩa sẽ không có khái niệm về thao tác khi sử dụng DSL.
Bước tiếp theo là xác định các tuyến mà bạn sẽ sử dụng khi xác định biểu đồ.
Tạo tuyến đường cho biểu đồ
Biểu đồ điều hướng dựa trên XML được phân tích cú pháp như một phần
của quy trình xây dựng Android. Một hằng số sẽ được tạo cho mỗi id
được xác định trong biểu đồ. Các mã tĩnh được tạo trong thời gian xây dựng này không
khi tạo biểu đồ điều hướng trong thời gian chạy, để DSL Navigation
sử dụng công cụ chuyển đổi tuần tự
loại thay vì
Mã nhận dạng. Mỗi tuyến đường được biểu thị bằng một loại duy nhất.
Khi xử lý đối số, các đối số này sẽ được tích hợp vào tuyến đường . Điều này cho phép bạn đảm bảo an toàn về kiểu cho các đối số điều hướng của bạn.
@Serializable data object Home
@Serializable data class Plant(val id: String)
Tạo biểu đồ bằng NavGraphBuilder DSL
Sau khi xác định các tuyến, bạn có thể tạo biểu đồ điều hướng.
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)
}
}
Trong ví dụ này, hai đích đến của mảnh được xác định bằng cách sử dụng phương thức
fragment()
Hàm tạo DSL. Hàm này yêu cầu 2 loại
đối số
của Google.
Trước tiên, một lớp Fragment
cung cấp giao diện người dùng cho đích đến này. Việc đặt giá trị này có tác dụng tương tự như
đặt thuộc tính android:name
trên các đích đến của mảnh đã được xác định
bằng XML.
Thứ hai là tuyến đường. Đây phải là một kiểu chuyển đổi tuần tự mở rộng từ Any
. Nó
phải chứa bất kỳ đối số điều hướng nào mà đích đến này sẽ sử dụng,
và loại của chúng.
Hàm này cũng chấp nhận một hàm lambda không bắt buộc cho cấu hình bổ sung, chẳng hạn như làm nhãn đích, cũng như các hàm trình tạo được nhúng cho đối số và liên kết sâu.
Điều hướng bằng biểu đồ Kotlin DSL
Cuối cùng, bạn có thể điều hướng từ home
đến plant_detail
bằng
NavController.navigate()
cuộc gọi:
private fun navigateToPlant(plantId: String) {
findNavController().navigate(route = PlantDetail(id = plantId))
}
Trong PlantDetailFragment
, bạn có thể lấy các đối số điều hướng bằng cách lấy
hiện tại
NavBackStackEntry
và gọi điện
toRoute
trên đó để lấy thực thể tuyến đường.
val plantDetailRoute = findNavController().getBackStackEntry<PlantDetail>().toRoute<PlantDetail>()
val plantId = plantDetailRoute.id
Nếu PlantDetailFragment
đang sử dụng ViewModel
, hãy lấy thực thể tuyến bằng cách sử dụng
SavedStateHandle.toRoute
.
val plantDetailRoute = savedStateHandle.toRoute<PlantDetail>()
val plantId = plantDetailRoute.id
Phần còn lại của hướng dẫn này sẽ mô tả các thành phần của biểu đồ điều hướng phổ biến, đích đến và cách sử dụng các yếu tố này khi tạo biểu đồ.
Đích đến
Kotlin DSL cung cấp cơ chế hỗ trợ được tích hợp sẵn cho ba loại đích đến: Fragment
, Activity
, và NavGraph
. Mỗi đích đến này đều có chức năng tiện ích cùng dòng (inline) riêng để tạo và định cấu hình cho đích đến đó.
Đích đến của mảnh
Chiến lược phát hành đĩa đơn
fragment()
Hàm DSL có thể được tham số bằng lớp mảnh cho giao diện người dùng và
loại tuyến dùng để nhận dạng duy nhất đích đến này, theo sau là một lambda
nơi bạn có thể cung cấp cấu hình bổ sung như được mô tả trong phần Điều hướng
với mục biểu đồ Kotlin DSL.
fragment<MyFragment, MyRoute> {
label = getString(R.string.fragment_title)
// custom argument types, deepLinks
}
Đích đến của hoạt động
Chiến lược phát hành đĩa đơn
activity()
Hàm DSL lấy một tham số loại cho tuyến đường nhưng không được tham số hoá thành
bất kỳ lớp hoạt động triển khai nào. Thay vào đó, bạn sẽ đặt một activityClass
(không bắt buộc) trong
trailing lambda (lambda theo sau). Tính linh hoạt này cho phép bạn xác định đích đến của hoạt động cho
một hoạt động phải được khởi chạy bằng cách sử dụng một giao diện ngầm ẩn
ý định, trong đó lệnh gọi rõ ràng
lớp hoạt động nào sẽ không hợp lý. Tương tự như với đích đến của mảnh, bạn cũng có thể
định cấu hình nhãn, đối số tuỳ chỉnh và đường liên kết sâu.
activity<MyRoute> {
label = getString(R.string.activity_title)
// custom argument types, deepLinks...
activityClass = MyActivity::class
}
Đích đến trên biểu đồ điều hướng
Chiến lược phát hành đĩa đơn
navigation()
Hàm DSL có thể được dùng để tạo điều hướng lồng nhau
biểu đồ. Hàm này nhận một loại
cho tuyến đường để gán cho biểu đồ này. Hàm này cũng có 2 đối số:
tuyến đường của đích đến bắt đầu trên biểu đồ và một lambda để
định cấu hình biểu đồ. Phần tử hợp lệ bao gồm các đích đến khác, đối số tuỳ chỉnh
loại, liên kết sâu và nhãn mô tả cho
đích.
Nhãn này có thể hữu ích cho việc liên kết biểu đồ điều hướng với các thành phần giao diện người dùng bằng cách sử dụng
NavigationUI
.
@Serializable data object HomeGraph
@Serializable data object Home
navigation<HomeGraph>(startDestination = Home) {
// label, other destinations, deep links
}
Hỗ trợ đích đến tuỳ chỉnh
Nếu bạn đang sử dụng một loại đích đến mới
không hỗ trợ trực tiếp Kotlin DSL, bạn có thể thêm các đích đến này vào
DSL Kotlin bằng cách sử dụng
addDestination()
:
// The NavigatorProvider is retrieved from the NavController
val customDestination = navigatorProvider[CustomNavigator::class].createDestination().apply {
route = Graph.CustomDestination.route
}
addDestination(customDestination)
Ngoài ra, bạn cũng có thể sử dụng toán tử cộng một ngôi (unary plus operator) để thêm trực tiếp điểm đến mới được tạo vào biểu đồ:
// The NavigatorProvider is retrieved from the NavController
+navigatorProvider[CustomNavigator::class].createDestination().apply {
route = Graph.CustomDestination.route
}
Cung cấp các đối số cho đích đến
Đối số đích có thể được định nghĩa như một phần của lớp định tuyến. Đây có thể là xác định theo cách tương tự như đối với mọi lớp Kotlin. Đối số bắt buộc là được khai báo là các loại không thể có giá trị rỗng và đối số không bắt buộc được định nghĩa theo giá trị mặc định giá trị.
Cơ chế cơ bản để biểu thị các tuyến và đối số của chúng là chuỗi
dựa trên cơ sở. Việc sử dụng chuỗi để lập mô hình các tuyến đường cho phép lưu trữ trạng thái điều hướng và
khôi phục từ ổ đĩa trong quá trình định cấu hình
các thay đổi và quy trình do hệ thống khởi tạo
tử. Vì lý do này,
mỗi đối số điều hướng cần phải được chuyển đổi tuần tự, tức là đối số đó phải có
phương thức chuyển đổi cách biểu diễn trong bộ nhớ của giá trị đối số thành
String
.
Chuyển đổi tuần tự Kotlin
trình bổ trợ
tự động tạo các phương thức chuyển đổi tuần tự cho cơ bản
khi
Chú giải @Serializable
được thêm vào một đối tượng.
@Serializable
data class MyRoute(
val id: String,
val myList: List<Int>,
val optionalArg: String? = null
)
fragment<MyFragment, MyRoute>
Cung cấp các kiểu tuỳ chỉnh
Đối với loại đối số tuỳ chỉnh, bạn cần cung cấp một lớp NavType
tuỳ chỉnh. Chiến dịch này
cho phép bạn kiểm soát chính xác cách phân tích cú pháp loại thông qua một tuyến hoặc đường liên kết sâu.
Ví dụ: một tuyến dùng để xác định màn hình tìm kiếm có thể chứa một lớp biểu thị các thông số tìm kiếm:
@Serializable
data class SearchRoute(val parameters: SearchParameters)
@Serializable
data class SearchParameters(
val searchQuery: String,
val filters: List<String>
)
NavType
tuỳ chỉnh có thể được viết thành:
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)
}
}
Sau đó, bạn có thể dùng kiểu tuỳ chỉnh này trong Kotlin DSL như bất kỳ kiểu nào khác:
fragment<SearchFragment, SearchRoute> {
label = getString(R.string.plant_search_title)
typeMap = mapOf(typeOf<SearchParameters>() to SearchParametersType)
}
Khi điều hướng tới đích đến, hãy tạo một thực thể của tuyến đường:
val params = SearchParameters("rose", listOf("available"))
navController.navigate(route = SearchRoute(params))
Tham số này có thể lấy từ tuyến trong đích đến:
val searchRoute = navController().getBackStackEntry<SearchRoute>().toRoute<SearchRoute>()
val params = searchRoute.parameters
Liên kết sâu
Bạn có thể thêm liên kết sâu vào bất kỳ đích đến nào, giống như cách thêm liên kết sâu vào biểu đồ điều hướng bằng XML. Tất cả quy trình tương tự được xác định trong bài viết Tạo đường liên kết sâu cho một đích đến sẽ áp dụng cho quy trình bằng cách sử dụng Kotlin DSL.
Khi tạo đường liên kết sâu ngầm ẩn
tuy nhiên, bạn lại không có tài nguyên điều hướng XML để có thể phân tích
Phần tử <deepLink>
. Do đó, bạn không thể dựa vào việc đặt <nav-graph>
trong tệp AndroidManifest.xml
và phải thêm ý định thay thế
vào hoạt động của mình theo cách thủ công. Ý định
bộ lọc mà bạn cung cấp phải khớp với đường dẫn cơ sở, hành động và mimetype của
liên kết sâu của ứng dụng.
Đường liên kết sâu được thêm vào một đích đến bằng cách gọi hàm deepLink
bên trong
lambda của đích đến. Phương thức này chấp nhận tuyến dưới dạng một loại có tham số và
tham số basePath
cho đường dẫn cơ sở của URL dùng cho đường liên kết sâu.
Bạn cũng có thể thêm hành động và mimetype bằng cách sử dụng
deepLinkBuilder
trailing lambda.
Ví dụ sau đây sẽ tạo một URI liên kết sâu cho đích đến Home
.
@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/*"
}
}
Định dạng URI
Định dạng URI liên kết sâu được tạo tự động từ các trường của tuyến bằng cách sử dụng các quy tắc sau:
- Các tham số bắt buộc được nối thêm dưới dạng thông số đường dẫn (ví dụ:
/{id}
) - Các thông số có giá trị mặc định (thông số không bắt buộc) sẽ được thêm vào dưới dạng truy vấn
tham số (ví dụ:
?name={name}
) - Bộ sưu tập được thêm vào dưới dạng tham số truy vấn (ví dụ:
?items={value1}&items={value2}
) - Thứ tự của các tham số khớp với thứ tự của các trường trong tuyến đường
Ví dụ: loại tuyến đường sau đây:
@Serializable data class PlantDetail(
val id: String,
val name: String,
val colors: List<String>,
val latinName: String? = null,
)
có định dạng URI được tạo là:
basePath/{id}/{name}/?colors={color1}&colors={color2}&latinName={latinName}
Không có giới hạn về số lượng liên kết sâu có thể thêm. Mỗi lần gọi deepLink()
, một liên kết sâu mới sẽ được bổ sung vào một danh sách được duy trì cho đích đến đó.
Các điểm hạn chế
Trình bổ trợ Ang Args không tương thích với Kotlin DSL vì trình bổ trợ này tìm các tệp tài nguyên XML để tạo các lớp Directions
và Arguments
.