在目的地之间传递数据

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

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

定义目的地参数

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

  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。

您可以从字符串中解析路由、深层链接和 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.4"
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
    }
}

Kotlin

buildscript {
    repositories {
        google()
    }
    dependencies {
        val nav_version = "2.8.4"
        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 传递给起始目的地:

如需检索起始目的地中的数据,请调用 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

...

其他资源

如需详细了解 Navigation,请参阅下面列出的其他资源:

Codelab

视频