Navigation 组件提供基于 Kotlin 的领域专用语言,或
DSL,它依赖于 Kotlin 的类型安全
构建器
,了解所有最新动态。借助此 API,您可以在 Kotlin 代码中以声明方式构建图表,
而不是在 XML 资源内如果您想构建自己应用的
动态导航。例如,您的应用可以下载并缓存
从外部网络服务获取导航配置,然后使用
用于在 activity 的 activity 中动态构建导航图的配置
onCreate()
函数。
依赖项
如需将 Kotlin DSL 与 Fragment 搭配使用,请将以下依赖项添加到应用的
build.gradle
文件:
Groovy
dependencies { def nav_version = "2.8.0" api "androidx.navigation:navigation-fragment-ktx:$nav_version" }
Kotlin
dependencies { val nav_version = "2.8.0" api("androidx.navigation:navigation-fragment-ktx:$nav_version") }
构建图表
下面是一个基于 Sunflower
app。为此
例如,我们有两个目的地:home
和 plant_detail
。用户首次启动应用时,系统会显示 home
目的地。该目标地会显示用户花园中的植物列表。当用户选择其中一种植物时,应用会导航到 plant_detail
目的地。
图 1 显示了这些目的地以及 plant_detail
目的地所需的参数和应用用于从 home
导航到 plant_detail
的操作 to_plant_detail
。
托管 Kotlin DSL 导航图
在构建应用的导航图之前,您需要一个托管
图表。此示例使用 fragment,因此它会将图表托管在 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()
的一部分
activity 中会发生的情况。
在 XML 中,操作用于将目的地 ID 与一个或多个实参绑定在一起。不过,使用导航 DSL 时,路线可以包含实参作为 路径。这意味着,在使用 DSL 时,不存在操作的概念。
下一步是定义您在定义 图表。
为图表创建路由
基于 XML 的导航图会作为一部分进行解析
Android 构建流程的各个环节。为每个 id
创建一个数字常量
属性。这些构建时生成的静态 ID
在运行时构建导航图时可用,因此导航 DSL
使用可序列化
类型,而不是
。每条路线都由一个唯一类型表示。
处理实参时,这些实参内置在路线中 type。这样可确保类型安全 。
@Serializable data object Home
@Serializable data class Plant(val id: String)
使用 NavGraphBuilder DSL 构建图表
定义路线后,您就可以构建导航图了。
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 构建器函数。此函数需要两个 type
实参
,了解所有最新动态。
首先,Fragment
类
,后者为此目的地提供界面。设置此项与
在定义的 fragment 目的地上设置 android:name
属性
使用 XML 格式
第二,路线它必须是从 Any
扩展的可序列化类型。它
应包含此目的地将使用的所有导航参数,
及其类型。
该函数还接受用于额外配置的可选 lambda,例如 作为目标标签,还用于自定义 参数和深层链接。
浏览 Kotlin DSL 图
最后,您可以使用以下命令从 home
导航到 plant_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 为 Fragment
、Activity
和 NavGraph
这三种类型的目的地提供了内置支持。每种类型都有专属的内嵌扩展函数,可用于构建和配置目的地。
fragment 目的地
通过
fragment()
可以使用界面的 Fragment 类和
用于唯一标识此目的地的路线类型,后跟 lambda
您可以在其中提供其他配置,如浏览
Kotlin DSL 图部分。
fragment<MyFragment, MyRoute> {
label = getString(R.string.fragment_title)
// custom argument types, deepLinks
}
activity 目的地
通过
activity()
DSL 函数接受路线的类型参数,但未参数化为
任何实现 activity 类。而是设置一个可选的 activityClass
尾随 lambda。这种灵活性可让您为
一个应使用隐式
intent,其中
activity 类毫无意义。与 fragment 目的地一样,您还可以
配置标签、自定义参数和深层链接。
activity<MyRoute> {
label = getString(R.string.activity_title)
// custom argument types, deepLinks...
activityClass = MyActivity::class
}
导航图目的地
通过
navigation()
DSL 函数可用于构建嵌套导航
图表。此函数采用类型
参数。它还采用两个参数:
图表起始目的地的路线,以及用于进一步
配置图表。有效元素包括其他目的地、自定义参数
深层链接和
目标位置。
此标签可用于使用以下代码将导航图绑定到界面组件
NavigationUI
。
@Serializable data object HomeGraph
@Serializable data object Home
navigation<HomeGraph>(startDestination = 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)
作为替代方案,您还可以使用一元加号运算符将新构造的目的地直接添加到图表中:
// The NavigatorProvider is retrieved from the NavController
+navigatorProvider[CustomNavigator::class].createDestination().apply {
route = Graph.CustomDestination.route
}
提供目的地实参
目的地参数可定义为路线类的一部分。这些可以是 定义方式与为任何 Kotlin 类定义相同。必需的参数为 定义为不可为 null 的类型,而可选参数则使用默认 值。
表示路由及其参数的底层机制是字符串
。使用字符串为路线建模,可存储导航状态,
在配置期间从磁盘恢复
变更和由系统发起的流程
死亡。因此
每个导航参数都需要可序列化,也就是说,它应该有一个
方法,该方法将参数值在内存中的表示形式转换为
String
。
Kotlin 序列化
插件
会自动为基本
类型时,
将 @Serializable
注解添加到对象中。
@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
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> {
label = getString(R.string.plant_search_title)
typeMap = mapOf(typeOf<SearchParameters>() to SearchParametersType)
}
导航到目的地时,创建路线实例:
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 创建深层链接。
创建隐式深层链接时
但您没有可用于分析的 XML 导航资源,
<deepLink>
元素。因此,您不能指望通过放置 <nav-graph>
元素,并且必须改为添加 intentAndroidManifest.xml
过滤器。目的
过滤器应该与以下文件的基本路径、操作和 MIME 类型
应用的深层链接
可通过调用浏览器中的 deepLink
函数,将深层链接添加到目的地。
目的地的 lambda。它接受作为参数化类型的路线,以及
参数 basePath
,表示用于深层链接的网址的基本路径。
您也可以使用
deepLinkBuilder
尾随 lambda。
以下示例将为 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 资源文件以生成 Directions
和 Arguments
类。