在目标之间传递数据

导航允许您通过为目标定义参数来将数据附加到导航操作。例如,用户配置文件目标可能会接收用户 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="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 可以从字符串中解析。使用自定义数据类型(例如 Parcelables 和 Serializables,如上一表所示)无法实现此操作。要传递自定义复杂数据,请将数据存储在其他位置(例如 ViewModel 或数据库),并在导航时仅传递标识符;然后在导航完成后检索新位置中的数据。

当您选择其中一种自定义类型时,将显示**选择类**对话框,并提示您为该类型选择相应的类。**项目**选项卡允许您从当前项目中选择一个类。

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

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

  • 不支持枚举数组和资源引用数组。
  • 数组支持空值,而不管底层类型是否支持空值。例如,使用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 以类型安全的方式传递数据

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

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

要将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")
}

您必须在gradle.properties文件中拥有android.useAndroidX=true,如迁移到 AndroidX中所述。

启用 Safe Args 后,生成的代码将包含以下类型安全类和方法,用于每个操作以及每个发送和接收目标。

  • 为每个操作源自的目标创建一个类。此类的名称是源目标的名称附加“Directions”一词。例如,如果源目标是名为SpecifyAmountFragment的片段,则生成的类名为SpecifyAmountFragmentDirections

    此类为源目标中定义的每个操作提供一个方法。

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

  • 为接收目标创建一个类。此类的名称是目标的名称附加“Args”一词。例如,如果目标片段名为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

...

其他资源

要了解有关导航的更多信息,请参阅以下其他资源。

示例

Codelabs

视频