Navigation 支持您通过定义目的地参数将数据附加到导航操作。例如,用户个人资料目的地可能会根据用户 ID 参数来确定要显示哪个用户。
通常情况下,强烈建议您仅在目的地之间传递最少量的数据。例如,您应该传递键来检索对象而不是传递对象本身,因为在 Android 上用于保存所有状态的总空间是有限的。如果您需要传递大量数据,请使用 ViewModel
,如 ViewModel 概览中所述。
定义目的地参数
如需在目的地之间传递数据,首先请按照以下步骤将参数添加到接收它的目的地来定义参数:
- 在 Navigation Editor 中,点击接收参数的目的地。
- 在 Attributes 面板中,点击 Add (+)。
- 在显示的 Add Argument Link 窗口中,输入参数名称、参数类型、参数是否可为 null,以及默认值(如果需要)。
- 点击 Add。请注意,该参数现在会显示在 Attributes 面板的 Arguments 列表中。
- 接下来,点击会将您转到此目的地的相应操作。在 Attributes 面板中,您现在应该会在 Argument Default Values 部分中看到新添加的参数。
您还可以看到该参数已添加到 XML 中。点击 Text 标签页以切换到 XML 视图,就会发现您的参数已添加到接收该参数的目的地。下方显示了一个示例:
<fragment android:id="@+id/myFragment" > <argument android:name="myArg" app:argType="integer" android:defaultValue="0" /> </fragment>
支持的参数类型
Navigation 库支持以下参数类型:
类型 | app:argType 语法 | 是否支持默认值? | 是否由路由处理? | 是否可为 null? |
---|---|---|---|---|
整数 | app:argType="integer" | 是 | 是 | 否 |
浮点数 | app:argType="float" | 是 | 是 | 否 |
长整数 | app:argType="long" | 是 - 默认值必须始终以“L”后缀结尾(例如“123L”)。 | 是 | 否 |
布尔值 | app:argType="boolean" | 是 -“true”或“false” | 是 | 否 |
字符串 | app:argType="string" | 是 | 是 | 是 |
资源引用 | app:argType="reference" | 是 - 默认值必须为“@resourceType/resourceName”格式(例如,“@style/myCustomStyle”)或“0” | 是 | 否 |
自定义 Parcelable | app:argType="<type>",其中 <type> 是 Parcelable 的完全限定类名称 |
支持默认值“@null”。不支持其他默认值。 | 否 | 是 |
自定义 Serializable | app:argType="<type>",其中 <type> 是 Serializable 的完全限定类名称 |
支持默认值“@null”。不支持其他默认值。 | 否 | 是 |
自定义 Enum | app:argType="<type>",其中 <type> 是 Enum 的完全限定名称 | 是 - 默认值必须与非限定名称匹配(例如,“SUCCESS”匹配 MyEnum.SUCCESS)。 | 否 | 否 |
如果参数类型支持 null 值,您可以使用 android:defaultValue="@null"
声明默认值 null。
您可以从字符串中解析路由、深层链接和 URI 及其参数,但无法使用自定义数据类型(如 Parcelable 和 Serializable),如上表所示。如需传递自定义复杂数据,请将数据存储在其他位置(如 ViewModel 或数据库),在导航过程中仅传递标识符;然后在导航结束后在新位置检索数据。
如果您选择其中一种自定义类型,系统会显示 Select Class 对话框,提示您选择与该类型对应的类。您可以通过 Project 标签页从当前项目中选择类。
您可以选择 <inferred type>,让 Navigation 库根据提供的值来确定类型。
您可以选中 Array,以指明参数应该是所选 Type 值的数组。请注意以下几点:
- 不支持枚举数组和资源引用数组。
- 无论基础类型是否支持可为 null 的值,数组都支持可为 null 的值。例如,使用
app:argType="integer[]"
时,您可以使用app:nullable="true"
来指示可传递 null 数组。 - 数组仅支持一个默认值,即“@null”。数组不支持任何其他默认值。
替换操作中的目的地参数
所有导航至目的地的操作都使用目的地级的参数和默认值。如果需要,您可以通过在操作级定义参数来替换参数的默认值(如果不存在,则设置一个)。此参数必须与目的地中声明的参数具有相同的名称和类型。
以下 XML 展示了如何声明操作来替换上例中的目的地级参数:
<action android:id="@+id/startMyFragment"
app:destination="@+id/myFragment">
<argument
android:name="myArg"
app:argType="integer"
android:defaultValue="1" />
</action>
使用 Safe Args 传递类型安全的数据
Navigation 组件具有一个名为 Safe Args 的 Gradle 插件,该插件可以生成简单的 object 和 builder 类,以便以类型安全的方式浏览和访问任何关联的参数。我们强烈建议您将 Safe Args 用于导航和数据传递,因为它可以确保类型安全。
如果您不使用 Gradle,则无法使用 Safe Args 插件。在这些情况下,您可以使用 Bundle 直接传递数据。
如需将 Safe Args 添加到您的项目,请在顶层 build.gradle
文件中包含以下 classpath
:
Groovy
buildscript { repositories { google() } dependencies { def nav_version = "2.8.0" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" } }
Kotlin
buildscript { repositories { google() } dependencies { val nav_version = "2.8.0" classpath("androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version") } }
您还必须应用以下两个可用插件之一。
如需生成适用于 Java 模块或 Java 和 Kotlin 混合模块的 Java 语言代码,请将以下行添加到应用或模块的 build.gradle
文件中:
Groovy
plugins { id 'androidx.navigation.safeargs' }
Kotlin
plugins { id("androidx.navigation.safeargs") }
此外,如需生成仅适用于 Kotlin 模块的 Kotlin 语言代码,请添加以下行:
Groovy
plugins { id 'androidx.navigation.safeargs.kotlin' }
Kotlin
plugins { id("androidx.navigation.safeargs.kotlin") }
根据迁移到 AndroidX 文档,您的 gradle.properties
文件中必须具有 android.useAndroidX=true
。
启用 Safe Args 后,生成的代码会为每个操作包含以下类型安全的类和方法,以及每个发送和接收目的地。
为生成操作的每一个目的地创建一个类。该类的名称是在源目的地的名称后面加上“Directions”。例如,如果源目的地是名为
SpecifyAmountFragment
的 fragment,则生成的类的名称为SpecifyAmountFragmentDirections
。该类会为源目的地中定义的每个操作提供一个方法。
对于用于传递参数的每个操作,都会创建一个 inner 类,该类的名称根据操作的名称确定。例如,如果操作名称为
confirmationAction,
,则类名称为ConfirmationAction
。如果您的操作包含不带defaultValue
的参数,则您可以使用关联的 action 类来设置参数值。为接收目的地创建一个类。该类的名称是在目的地的名称后面加上“Args”。例如,如果目的地 Fragment 的名称为
ConfirmationFragment,
,则生成的类的名称为ConfirmationFragmentArgs
。可以使用该类的fromBundle()
方法检索参数。
以下示例说明了如何使用这些方法来设置参数并将其传递给 navigate()
方法:
Kotlin
override fun onClick(v: View) { val amountTv: EditText = view!!.findViewById(R.id.editTextAmount) val amount = amountTv.text.toString().toInt() val action = SpecifyAmountFragmentDirections.confirmationAction(amount) v.findNavController().navigate(action) }
Java
@Override public void onClick(View view) { EditText amountTv = (EditText) getView().findViewById(R.id.editTextAmount); int amount = Integer.parseInt(amountTv.getText().toString()); ConfirmationAction action = SpecifyAmountFragmentDirections.confirmationAction(); action.setAmount(amount); Navigation.findNavController(view).navigate(action); }
在接收目的地的代码中,使用 getArguments()
方法检索 bundle 并使用其内容。使用 -ktx
依赖项时,Kotlin 用户还可以使用 by navArgs()
属性委托来访问参数。
Kotlin
val args: ConfirmationFragmentArgs by navArgs() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val tv: TextView = view.findViewById(R.id.textViewAmount) val amount = args.amount tv.text = amount.toString() }
Java
@Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { TextView tv = view.findViewById(R.id.textViewAmount); int amount = ConfirmationFragmentArgs.fromBundle(getArguments()).getAmount(); tv.setText(amount + ""); }
将 Safe Args 用于全局操作
将 Safe Args 用于全局操作时,您必须为根 <navigation>
元素提供一个 android:id
值,如以下示例中所示:
<?xml version="1.0" encoding="utf-8"?> <navigation xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/main_nav" app:startDestination="@id/mainFragment"> ... </navigation>
Navigation 会根据 android:id
值为 <navigation>
元素生成一个 Directions
类。例如,如果您有具有 android:id=@+id/main_nav
的 <navigation>
元素,则生成的类的名称为 MainNavDirections
。<navigation>
元素中的所有目的地都生成了方法,以使用前一节中所述的相同方法来访问所有关联的全局操作。
使用 Bundle 对象在目的地之间传递参数
如果您不使用 Gradle,仍然可以使用 Bundle
对象在目的地之间传递参数。创建一个 Bundle
对象,并使用 navigate()
将其传递给目的地,如以下示例所示:
Kotlin
val bundle = bundleOf("amount" to amount) view.findNavController().navigate(R.id.confirmationAction, bundle)
Java
Bundle bundle = new Bundle(); bundle.putString("amount", amount); Navigation.findNavController(view).navigate(R.id.confirmationAction, bundle);
在接收目的地的代码中,请使用 getArguments()
方法来检索 Bundle
并使用其内容:
Kotlin
val tv = view.findViewById<TextView>(R.id.textViewAmount) tv.text = arguments?.getString("amount")
Java
TextView tv = view.findViewById(R.id.textViewAmount); tv.setText(getArguments().getString("amount"));
将数据传递给起始目的地
您可以将数据传递给应用的起始目的地。首先,您必须显式构建一个 Bundle
来存储数据。然后,使用以下方法之一将该 Bundle
传递给起始目的地:
- 如果您要以程序化方式创建
NavHost
,请调用NavHostFragment.create(R.navigation.graph, args)
,其中args
是存储数据的Bundle
。 - 或者,您也可以通过调用以下
NavController.setGraph()
过载之一来设置起始目的地参数:
如需检索起始目的地中的数据,请调用 Fragment.getArguments()
。
ProGuard 注意事项
如果要缩减代码,您需要防止在缩减过程中混淆 Parcelable
、Serializable
和 Enum
类名称。为此,您可以采用下列两种方式之一:
- 使用 @Keep 注解
- 使用 keepnames 规则
以下小节概述了这些方法。
使用 @Keep 注解
以下示例展示了如何在模型类定义中添加 @Keep
注解:
Kotlin
@Keep class ParcelableArg : Parcelable { ... } @Keep class SerializableArg : Serializable { ... } @Keep enum class EnumArg { ... }
Java
@Keep public class ParcelableArg implements Parcelable { ... } @Keep public class SerializableArg implements Serializable { ... } @Keep public enum EnumArg { ... }
使用 keepnames 规则
您也可以将 keepnames
规则添加到您的 proguard-rules.pro
文件中,如以下示例中所示:
proguard-rules.pro
...
-keepnames class com.path.to.your.ParcelableArg
-keepnames class com.path.to.your.SerializableArg
-keepnames class com.path.to.your.EnumArg
...
其他资源
如需详细了解 Navigation,请参阅下面列出的其他资源: