在目的地之间传递数据

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。

您可以从字符串中解析路由、深层链接和 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 用于导航和数据传递,因为它可以确保类型安全。

在某些情况下您无法使用 Safe Args 插件,例如当您不使用 Gradle 时。在这些情况下,您可以使用 Bundle 直接传递数据。

To add Safe Args to your project, include the following classpath in your top level build.gradle file:

Groovy

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

Kotlin

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

You must also apply one of two available plugins.

To generate Java language code suitable for Java or mixed Java and Kotlin modules, add this line to your app or module's build.gradle file:

Groovy

plugins {
  id 'androidx.navigation.safeargs'
}

Kotlin

plugins {
    id("androidx.navigation.safeargs")
}

Alternatively, to generate Kotlin code suitable for Kotlin-only modules add:

Groovy

plugins {
  id 'androidx.navigation.safeargs.kotlin'
}

Kotlin

plugins {
    id("androidx.navigation.safeargs.kotlin")
}

You must have android.useAndroidX=true in your gradle.properties file as per Migrating to AndroidX.

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

以下示例说明了如何在模型类定义中添加 @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

视频