在目的地之间传递数据

Navigation 支持您通过定义目的地参数将数据附加到导航操作。例如,用户个人资料目的地可能会根据用户 ID 参数来确定要显示哪个用户。

通常情况下,强烈建议您仅在目的地之间传递最少量的数据。例如,您应该传递键来检索对象而不是传递对象本身,因为在 Android 上用于保存所有状态的总空间是有限的。如果需要传递大量数据,不妨考虑使用 ViewModel(如在 Fragment 之间共享数据中所述)。

定义目的地参数

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

  1. Navigation Editor 中,点击接收参数的目的地。
  2. Attributes 面板中,点击 Add (+)。
  3. 在显示的 Add Argument Link 窗口中,输入参数名称、参数类型、参数是否可为 null,以及默认值(若需要)。
  4. 点击 Add。注意该参数现在会显示在 Attributes 面板的 Arguments 列表中。
  5. 接下来,点击将您带到这个目的地的相应操作。在 Attributes 面板中,您现在会在 Argument Default Values 部分中看到新添加的参数。
  6. 您还可以看到该参数已添加到 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。

如果选择其中一种自定义类型,将会显示 Select Class 对话框,提示您选择与该类型对应的类。您可以通过 Project 标签从当前项目中选择类。

您可以选择 <inferred type>,让 Navigation 库根据提供的值来确定类型。

您可以选中 Array,以指明参数应该是所选 Type 值的数组。请注意,不支持枚举数组和资源引用数组。不论基础类型的 null 性如何,数组始终可为 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 时。在这些情况下,可以使用 Bundle 直接传递数据。

启用 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

    var 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 注释

以下示例说明了如何在模型类定义中添加 @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,请参阅下面列出的其他资源:

示例

Codelab

视频