fragment 和 Kotlin DSL

Navigation 组件提供基于 Kotlin 的领域专用语言,或 DSL,它依赖于 Kotlin 的类型安全 构建器 ,了解所有最新动态。借助此 API,您可以在 Kotlin 代码中以声明方式构建图表, 而不是在 XML 资源内如果您想构建自己应用的 动态导航。例如,您的应用可以下载并缓存 从外部网络服务获取导航配置,然后使用 用于在 activity 的 activity 中动态构建导航图的配置 onCreate() 函数。

依赖项

如需将 Kotlin DSL 与 Fragment 搭配使用,请将以下依赖项添加到应用的 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")
}

构建图表

下面是一个基于 Sunflower app。为此 例如,我们有两个目的地:homeplant_detail。用户首次启动应用时,系统会显示 home 目的地。该目标地会显示用户花园中的植物列表。当用户选择其中一种植物时,应用会导航到 plant_detail 目的地。

图 1 显示了这些目的地以及 plant_detail 目的地所需的参数和应用用于从 home 导航到 plant_detail 的操作 to_plant_detail

Sunflower 应用有两个目的地以及一个连接这两个目的地的操作。
图 1. Sunflower 应用有两个目的地(homeplant_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)

定义路线后,您就可以构建导航图了。

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,例如 作为目标标签,还用于自定义 参数和深层链接。

最后,您可以使用以下命令从 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 为 FragmentActivityNavGraph 这三种类型的目的地提供了内置支持。每种类型都有专属的内嵌扩展函数,可用于构建和配置目的地。

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 资源文件以生成 DirectionsArguments 类。