在目的地之间传递数据

导航允许您通过为目的地定义参数来将数据附加到导航操作。例如,用户个人资料目的地可能需要一个用户 ID 参数来确定要显示哪个用户。

通常,您应强烈建议在目的地之间仅传递最少量的数据。例如,您应传递一个键来检索对象,而不是直接传递对象本身,因为 Android 上所有已保存状态的总空间是有限的。如果您需要传递大量数据,请使用 ViewModel,如ViewModel 概览中所述。

定义目的地参数

要在目的地之间传递数据,请首先按照以下步骤将参数添加到接收它的目的地:

  1. 导航编辑器中,点击接收参数的目的地。
  2. 属性面板中,点击添加 (+)。
  3. 在出现的添加参数链接窗口中,输入参数名称、参数类型、参数是否可为空,以及默认值(如果需要)。
  4. 点击添加。请注意,该参数现在会出现在属性面板中的参数列表中。
  5. 接下来,点击将您带到此目的地的相应操作。在属性面板中,您现在应该会在参数默认值部分看到您新添加的参数。
  6. 您还可以看到该参数已添加到 XML 中。点击文本标签页切换到 XML 视图,并注意您的参数已添加到接收该参数的目的地。示例如下所示:

     <fragment android:id="@+id/myFragment" >
         <argument
             android:name="myArg"
             app:argType="integer"
             android:defaultValue="0" />
     </fragment>
    

支持的参数类型

导航库支持以下参数类型:

类型 app:argType 语法 支持默认值 由路由处理 可为空
整数 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”作为默认值。不支持其他默认值。
自定义枚举 app:argType="<type>",其中 <type> 是枚举的完全限定名称 是 - 默认值必须与非限定名称匹配(例如,“SUCCESS”匹配 MyEnum.SUCCESS)。

如果参数类型支持空值,您可以使用 android:defaultValue="@null" 声明一个空默认值。

路由、深层链接和带参数的 URI 可以从字符串中解析。使用自定义数据类型(如 Parcelable 和 Serializable)无法实现这一点,如上表所示。要传递自定义复杂数据,请将数据存储在其他地方(如 ViewModel 或数据库),并在导航时仅传递一个标识符;然后,在导航结束后在新位置检索数据。

当您选择其中一个自定义类型时,会显示选择类对话框,提示您为该类型选择相应的类。项目标签页允许您从当前项目中选择一个类。

您可以选择<推断类型>,让导航库根据提供的值确定类型。

您可以勾选数组以指示参数应为所选类型值的数组。请注意以下事项:

  • 不支持枚举数组和资源引用数组。
  • 数组支持可为空的值,无论底层类型是否支持可为空的值。例如,使用 app:argType="integer[]" 允许您使用 app:nullable="true" 来指示传递空数组是可接受的。
  • 数组支持单个默认值“@null”。数组不支持任何其他默认值。

在操作中覆盖目的地参数

目的地级别的参数和默认值由导航到该目的地的所有操作使用。如果需要,您可以通过在操作级别定义参数来覆盖参数的默认值(如果不存在,则设置一个)。此参数必须与目的地中声明的参数具有相同的名称和类型。

以下 XML 声明了一个带参数的操作,该参数会覆盖前面示例中的目的地级别参数:

<action android:id="@+id/startMyFragment"
    app:destination="@+id/myFragment">
    <argument
        android:name="myArg"
        app:argType="integer"
        android:defaultValue="1" />
</action>

使用 Safe Args 传递类型安全的数据

导航组件有一个名为 Safe Args 的 Gradle 插件,它生成简单的对象和构建器类,用于类型安全的导航和访问任何关联的参数。强烈建议使用 Safe Args 进行导航和数据传递,因为它确保了类型安全。

如果您未使用 Gradle,则无法使用 Safe Args 插件。在这种情况下,您可以使用 Bundle 直接传递数据。

要将Safe Args添加到您的项目,请在顶层 build.gradle 文件中包含以下 classpath

Groovy

buildscript {
    repositories {
        google()
    }
    dependencies {
        def nav_version = "2.9.0"
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
    }
}

Kotlin

buildscript {
    repositories {
        google()
    }
    dependencies {
        val nav_version = "2.9.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

    此类为源目的地中定义的每个操作都包含一个方法。

  • 对于用于传递参数的每个操作,都会创建一个内部类,其名称基于该操作。例如,如果操作名为 confirmationAction,,则该类名为 ConfirmationAction。如果您的操作包含没有 defaultValue 的参数,则您可以使用关联的操作类来设置参数的值。

  • 为接收目的地创建一个类。此类的名称是目的地名称后附加单词“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> 元素生成一个 Directions 类,该类基于 android:id 值。例如,如果您有一个 <navigation> 元素,其 android:id=@+id/main_nav,则生成的类名为 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 传递给起始目的地:

要在起始目的地中检索数据,请调用 Fragment.getArguments()

ProGuard 注意事项

如果您正在压缩代码,则需要防止您的 ParcelableSerializableEnum 类名在混淆过程中被混淆。您可以通过以下两种方法之一实现此目的:

  • 使用 @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

...

额外资源

要详细了解导航,请查阅以下额外资源。

Codelab

视频