【Jetpack】Navigation 导航组件 ④ ( Fragment 跳转中使用 safe args 安全传递参数 )


代码地址 :





一、页面跳转间的传统的数据传递方式




1、传统的数据传递方式 - Bundle 传递数据



1、Navigation 组件中的 Bundle 数据传递


之前的 默认 Navigation 跳转方法 , 只需要传入 navigation 资源 ID , 即可完成页面跳转 ;

public open fun navigate(@IdRes resId: Int)

Navigation 机制中 , 还提供了可以传入 Bundle 参数的跳转方法 , 调用该方法 , 可以在页面跳转时 , 传递一个 Bundle 参数 , 其中可以封装一系列的参数键值对 ;

public open fun navigate(@IdRes resId: Int, args: Bundle?)

2、传统数据传递实现步骤


首先 , 创建 Bundle 实例对象 , 向其中封装 “NAME” = “Tom” , “AGE” = 18 , 两组数据 ;

// 定义 Kotlin 常量
private const val ARG_PARAM_NAME = "NAME"
private const val ARG_PARAM_AGE = "AGE"

// 正常方式传递参数
var args: Bundle = Bundle().apply {
    // 设置 Bundle 对象参数数据
    this.putString(ARG_PARAM_NAME, "Tom")
    this.putInt(ARG_PARAM_AGE, 18)
}

然后 , 调用 Navigation#findNavController 函数 , 获取 NavigationController ;

// 获取 NavigationController
val navController = Navigation.findNavController(it)
// 按照 action_fragmentA_to_fragmentB 对应的 action 的导航路线走
navController.navigate(R.id.action_fragmentA_to_fragmentB, args)

再后 , 调用 NavigationController#navigate 方法 , 传入对应的 Navigation 导航资源 和 要传递的 Bundle 参数 ;

// 按照 action_fragmentA_to_fragmentB 对应的 action 的导航路线走
navController.navigate(R.id.action_fragmentA_to_fragmentB, args)

最后 , 在跳转后的界面中 , 调用 getArguments 函数 , 并获取 NAME 和 AGE 对应的参数值 ;

// 定义 Kotlin 常量
private const val ARG_PARAM_NAME = "NAME"
private const val ARG_PARAM_AGE = "AGE"

arguments?.let {
    name = it.getString(ARG_PARAM_NAME)
    age = it.getInt(ARG_PARAM_AGE)
}

3、FragmentA 完整代码示例


FragmentA 完整代码示例 :

package kim.hsl.nav

import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.navigation.Navigation

// 定义 Kotlin 常量
private const val ARG_PARAM_NAME = "NAME"
private const val ARG_PARAM_AGE = "AGE"

class FragmentB : Fragment() {
    private var name: String? = null
    private var age: Int? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            name = it.getString(ARG_PARAM_NAME)
            age = it.getInt(ARG_PARAM_AGE)
        }

        Log.i("TAG", "FragmentA 传递到 FragmentB 的参数为 name = $name , age = $age")
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // 设置 Fragment 布局文件
        return inflater.inflate(R.layout.fragment_b, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val button = view.findViewById<Button>(R.id.button)
        button.setOnClickListener {
            // 获取 NavigationController
            val navController = Navigation.findNavController(it)
            // 按照 action_fragmentB_to_fragmentA 对应的 action 的导航路线走
            navController.navigate(R.id.action_fragmentB_to_fragmentA)
        }
    }
}

4、FragmentB 完整代码示例


FragmentB 完整代码示例 :

package kim.hsl.nav

import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.navigation.Navigation

// 定义 Kotlin 常量
private const val ARG_PARAM_NAME = "NAME"
private const val ARG_PARAM_AGE = "AGE"

class FragmentB : Fragment() {
    private var name: String? = null
    private var age: Int? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            name = it.getString(ARG_PARAM_NAME)
            age = it.getInt(ARG_PARAM_AGE)
        }

        Log.i("TAG", "FragmentA 传递到 FragmentB 的参数为 name = $name , age = $age")
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // 设置 Fragment 布局文件
        return inflater.inflate(R.layout.fragment_b, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val button = view.findViewById<Button>(R.id.button)
        button.setOnClickListener {
            // 获取 NavigationController
            val navController = Navigation.findNavController(it)
            // 按照 action_fragmentB_to_fragmentA 对应的 action 的导航路线走
            navController.navigate(R.id.action_fragmentB_to_fragmentA)
        }
    }
}

5、执行结果


运行应用 , 进入界面后 , 自动进入 默认的 FragmentA 界面 ,

在这里插入图片描述

点击 " 跳转到 B " , 此时 , 跳转到 FragmentB 界面 :
在这里插入图片描述

此时 Logcat 日志面板 , 输出如下内容 :

kim.hsl.nav I/TAG: FragmentA 传递到 FragmentB 的参数为 name = Tom , age = 18

在这里插入图片描述


2、使用 Bundle 传递数据安全性差


使用 传统的方式 , 在 Fragment 之间 传递 数据 , 类型很不安全 ,

设置 传递的数据时 , 需要设置 放入的 数据类型 , 如下代码所示 :

// 正常方式传递参数
var args: Bundle = Bundle().apply {
    // 设置 Bundle 对象参数数据
    this.putString("NAME", "Tom")
    this.putInt("AGE", 18)
}

上面的代码中 , 向 Bundle 中设置了如下两个数据 :

  • 设置了 String 类型的数据 , 名称是 “NAME” 字符串常量 , 值为 字符串 “Tom” ,
  • 设置了 Int 类型的数据 , 名称是 “AGE” 字符串常量 , 值为 整型 18 ;

这里要注意 , 设置的时候 , 设置的 NAME 属性值是 String 类型的 , 那么在 FragmentB 中获取的 NAME 属性值也必须是 String 类型的 ,

arguments?.let {
    name = it.getString("NAME")
}

此处 没有 类型检查 , 即使你写错了具体的 属性值 名称 和 属性值 类型 , 编译器也不会报错 , 但是在执行时 , 会出现错误 ;

下面的代码中 , 调用 getInt(“Name”) 也不会报错 ;

在这里插入图片描述

上面的 使用 Bundle 在 Fragment 之间传递 参数 , 没有类型检查 , 即使写错了数据类型 也不会报错 , 这就导致了 数据传递 不安全 的问题 , 如果出现问题 , 导致错误很难排查 ;





二、页面跳转间的传统的数据传递方式




1、导入插件依赖


安全参数传递需要使用到 androidx.navigation:navigation-safe-args-gradle-plugin:2.3.0-alpha06 中的 androidx.navigation.safeargs 插件 ;

由于在最新版的 Gradle 配置中 , 使用 根目录下 build.gradle 构建脚本中的 直接配置 plugins 插件的方式 , 无法获取到该 androidx.navigation.safeargs 插件 , 因此放弃该方案 , 将 该脚本的 整个 plugins 代码块完全注释掉 ;

plugins {
    id 'com.android.application' version '7.3.1' apply false
    id 'com.android.library' version '7.3.1' apply false
    id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
    id 'androidx.navigation.safeargs' version '2.3.0-alpha06' apply false
}

在这里插入图片描述

在 settings.gradle 中 , 使用传统的方式配置 Gralde 编译过程中使用到的插件 ;

下面的章节中 , 可以查看该 settings.gradle 配置的完整源码 ;


配置如下 :

buildscript {
    repositories {
        google()
        mavenCentral()
        jcenter()
        maven {
            url 'https://maven.aliyun.com/repository/public/'
        }
        maven{
            url 'https://maven.aliyun.com/repository/google/'
        }
    }
    dependencies {
        classpath "com.android.tools.build:gradle:7.3.1"
        classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.3.0-alpha06'
        classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

2、使用插件


在 Module 下的 build.gradle 中 , 使用 androidx.navigation.safeargs 依赖 ;

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'androidx.navigation.safeargs'
}

3、在 navigation_graph.xml 中定义要传递的 argument 参数信息


如果要从 FragmentB 跳转到 FragmentA 页面时 , 传递数据 , 就将参数信息设置在该 FragmentB 对应的配置文件中 ;


参数格式为 :

        <argument
            android:name="NAME"
            app:argType="string"
            android:defaultValue="Jerry"/>
  • 参数名称为 " NAME " ;
  • 参数类型是 string 类型 ;
  • 参数默认值是 “Jerry” ;

完整的参数配置如下 :

    <fragment
        android:id="@+id/fragmentB"
        android:name="kim.hsl.nav.FragmentB"
        android:label="fragment_b"
        tools:layout="@layout/fragment_b" >
        <action
            android:id="@+id/action_fragmentB_to_fragmentA"
            app:destination="@id/fragmentA"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim" />

        <!-- 配置完毕后 菜单栏/Build/Make 编译一下,
             自动生成 FragmentBArgs.java 代码, 之后调用该自动生成的类传参 -->
        <argument
            android:name="NAME"
            app:argType="string"
            android:defaultValue="Jerry"/>
        <argument
            android:name="AGE"
            app:argType="integer"
            android:defaultValue="12"/>
    </fragment>

4、重新编译生成参数传递相关代码


FragmentB 中 配置完毕 参数相关配置 后 , 选择 " 菜单栏 / Build / Make " 选项 , 重新编译一下,

目的是为了 生成 FragmentBArgs.java 代码, 之后调用该自动生成的类 进行 传参 ;


生成的类在 " Navigationappbuildgeneratedsourcenavigation-argsdebugkimhslnav " 目录下 ,

在这里插入图片描述


生成的 FragmentBArgs.java 代码如下 : ( 仅做参考 )

package kim.hsl.nav;

import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.navigation.NavArgs;
import java.lang.IllegalArgumentException;
import java.lang.Object;
import java.lang.Override;
import java.lang.String;
import java.lang.SuppressWarnings;
import java.util.HashMap;

public class FragmentBArgs implements NavArgs {
  private final HashMap arguments = new HashMap();

  private FragmentBArgs() {
  }

  private FragmentBArgs(HashMap argumentsMap) {
    this.arguments.putAll(argumentsMap);
  }

  @NonNull
  @SuppressWarnings("unchecked")
  public static FragmentBArgs fromBundle(@NonNull Bundle bundle) {
    FragmentBArgs __result = new FragmentBArgs();
    bundle.setClassLoader(FragmentBArgs.class.getClassLoader());
    if (bundle.containsKey("NAME")) {
      String NAME;
      NAME = bundle.getString("NAME");
      if (NAME == null) {
        throw new IllegalArgumentException("Argument "NAME" is marked as non-null but was passed a null value.");
      }
      __result.arguments.put("NAME", NAME);
    } else {
      __result.arguments.put("NAME", "Jerry");
    }
    if (bundle.containsKey("AGE")) {
      int AGE;
      AGE = bundle.getInt("AGE");
      __result.arguments.put("AGE", AGE);
    } else {
      __result.arguments.put("AGE", 12);
    }
    return __result;
  }

  @SuppressWarnings("unchecked")
  @NonNull
  public String getNAME() {
    return (String) arguments.get("NAME");
  }

  @SuppressWarnings("unchecked")
  public int getAGE() {
    return (int) arguments.get("AGE");
  }

  @SuppressWarnings("unchecked")
  @NonNull
  public Bundle toBundle() {
    Bundle __result = new Bundle();
    if (arguments.containsKey("NAME")) {
      String NAME = (String) arguments.get("NAME");
      __result.putString("NAME", NAME);
    } else {
      __result.putString("NAME", "Jerry");
    }
    if (arguments.containsKey("AGE")) {
      int AGE = (int) arguments.get("AGE");
      __result.putInt("AGE", AGE);
    } else {
      __result.putInt("AGE", 12);
    }
    return __result;
  }

  @Override
  public boolean equals(Object object) {
    if (this == object) {
        return true;
    }
    if (object == null || getClass() != object.getClass()) {
        return false;
    }
    FragmentBArgs that = (FragmentBArgs) object;
    if (arguments.containsKey("NAME") != that.arguments.containsKey("NAME")) {
      return false;
    }
    if (getNAME() != null ? !getNAME().equals(that.getNAME()) : that.getNAME() != null) {
      return false;
    }
    if (arguments.containsKey("AGE") != that.arguments.containsKey("AGE")) {
      return false;
    }
    if (getAGE() != that.getAGE()) {
      return false;
    }
    return true;
  }

  @Override
  public int hashCode() {
    int result = 1;
    result = 31 * result + (getNAME() != null ? getNAME().hashCode() : 0);
    result = 31 * result + getAGE();
    return result;
  }

  @Override
  public String toString() {
    return "FragmentBArgs{"
        + "NAME=" + getNAME()
        + ", AGE=" + getAGE()
        + "}";
  }

  public static class Builder {
    private final HashMap arguments = new HashMap();

    public Builder(FragmentBArgs original) {
      this.arguments.putAll(original.arguments);
    }

    public Builder() {
    }

    @NonNull
    public FragmentBArgs build() {
      FragmentBArgs result = new FragmentBArgs(arguments);
      return result;
    }

    @NonNull
    public Builder setNAME(@NonNull String NAME) {
      if (NAME == null) {
        throw new IllegalArgumentException("Argument "NAME" is marked as non-null but was passed a null value.");
      }
      this.arguments.put("NAME", NAME);
      return this;
    }

    @NonNull
    public Builder setAGE(int AGE) {
      this.arguments.put("AGE", AGE);
      return this;
    }

    @SuppressWarnings("unchecked")
    @NonNull
    public String getNAME() {
      return (String) arguments.get("NAME");
    }

    @SuppressWarnings("unchecked")
    public int getAGE() {
      return (int) arguments.get("AGE");
    }
  }
}

5、调用 FragmentBArgs 类生成参数 Bundle


在 FragmentB 中 ,

首先 , 调用 FragmentBArgs#Builder() , 创建 参数创建者类 ,

然后 , 调用 setNAME 和 setAGE 分别设置 参数 ,

再后 , 调用 FragmentBArgs.Builder#build() 函数 , 创建 FragmentBArgs 类型的 参数对象 ,

最后 , 调用 FragmentBArgs#toBundle() 函数 , 将 FragmentBArgs 对象转为 Bundle 类型对象 ;

            var args: Bundle = FragmentBArgs.Builder()
                                            .setNAME("Trump")
                                            .setAGE(80)
                                            .build().toBundle()

创建完 Bundle 对象之后 , 将其传给 NavigationController#navigate 函数 , 进行页面跳转 ;

var args: Bundle = FragmentBArgs.Builder()
                                .setNAME("Trump")
                                .setAGE(80)
                                .build().toBundle()
// 获取 NavigationController
val navController = Navigation.findNavController(it)
// 按照 action_fragmentB_to_fragmentA 对应的 action 的导航路线走
navController.navigate(R.id.action_fragmentB_to_fragmentA, args)

后续章节可以查看 FragmentB 的完整代码 ;


6、FragmentA 中获取参数


在 FragmentA 中 , 调用 getArguments 函数 , 获取页面跳转传递的 Bundle 对象即可 ;

        arguments?.let {
            name = it.getString(ARG_PARAM_NAME)
            age = it.getInt(ARG_PARAM_AGE)
        }

        Log.i("TAG", "FragmentB 传递到 FragmentA 的参数为 name = $name , age = $age")




三、两种传参方式的完整代码示例




1、Gradle 构建脚本



I、根目录下 settings.gradle 构建脚本


该构建脚本中 , pluginManagement 是最新的 Gradle 配置 , 但是本项目中没有启用 , 注释掉也可以运行 ;

buildscript 是老版本的 Gradle 编译时依赖配置 , 由于本次使用了 androidx.navigation.safeargs 插件 , 该依赖使用新方式配置无法成功下载 , 这里直接使用老的配置方式 ;

dependencyResolutionManagement 中配置的是依赖库的下载地址 ;


settings.gradle 构建脚本代码示例 :

pluginManagement {
    repositories {
        gradlePluginPortal()
        google()
        mavenCentral()
        jcenter()
        maven {
            url 'https://maven.aliyun.com/repository/public/'
        }
        maven{
            url 'https://maven.aliyun.com/repository/google/'
        }
    }
}

buildscript {
    repositories {
        google()
        mavenCentral()
        jcenter()
        maven {
            url 'https://maven.aliyun.com/repository/public/'
        }
        maven{
            url 'https://maven.aliyun.com/repository/google/'
        }
    }
    dependencies {
        classpath "com.android.tools.build:gradle:7.3.1"
        classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.3.0-alpha06'
        classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}
rootProject.name = "Navigation"
include ':app'

II、根目录下 build.gradle 构建脚本


这是新的 Gradle 语法配置 , 需要结合 pluginManagement 配置使用 , 由于下面的配置无法成功下载 androidx.navigation.safeargs 依赖 , 整体作废 ;


根目录下 build.gradle 构建脚本 :

// Top-level build file where you can add configuration options common to all sub-projects/modules.
/*plugins {
    id 'com.android.application' version '7.3.1' apply false
    id 'com.android.library' version '7.3.1' apply false
    id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
    id 'androidx.navigation.safeargs' version '2.3.0-alpha06' apply false
}*/

III、Module 目录下 build.gradle 构建脚本


该配置没有需要注意的 , 导入 androidx.navigation.safeargs 插件就行 ;


Module 目录下 build.gradle 构建脚本 :

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'androidx.navigation.safeargs'
}

android {
    namespace 'kim.hsl.nav'
    compileSdk 32

    defaultConfig {
        applicationId "kim.hsl.nav"
        minSdk 21
        targetSdk 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

dependencies {

    implementation 'androidx.core:core-ktx:1.7.0'
    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.5.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
    implementation 'androidx.navigation:navigation-fragment-ktx:2.4.1'
    implementation 'androidx.navigation:navigation-ui-ktx:2.4.1'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

2、res 资源配置


Resources 资源配置 , 主要是配置 Navigation 相关的 NavigationGraph ;


I、MainActivity 页面布局


这是 主页面 Launcher Activity 的布局 , 之后的 Fragment 的 布局 就替换到 fragment 标签位置 ;


MainActivity 页面布局 :

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <fragment
        android:id="@+id/fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/navigation_graph" />

</androidx.constraintlayout.widget.ConstraintLayout>

II、FragmentA 页面布局


页面布局就是一个简单的 FrameLayout 布局 , 要先创建 Fragment 布局 , 然后才能在 navigation_graph.xml 配置该布局 ;


FragmentA 页面布局 :

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".FragmentA">

    <!-- TODO: Update blank fragment layout -->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="@string/hello_blank_fragment" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="跳转到 B"
        android:onClick="onClick" />

</FrameLayout>

III、FragmentB 页面布局


页面布局就是一个简单的 FrameLayout 布局 , 要先创建 Fragment 布局 , 然后才能在 navigation_graph.xml 配置该布局 ;


FragmentB 页面布局 :

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".FragmentB">

    <!-- TODO: Update blank fragment layout -->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="@string/hello_blank_fragment" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="跳转到 A"
        android:onClick="onClick" />

</FrameLayout>

IV、navigation_graph.xml 配置


在 res 目录下 , 创建 navigation 目录 , 然后在该目录中创建 navigation_graph.xml 配置文件 , 用于配置 页面跳转 相关参数 ;

具体的参数含义 , 可以参考之前的博客 ;


navigation_graph.xml 配置 :

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/navigation_graph"
    app:startDestination="@id/fragmentA">

    <fragment
        android:id="@+id/fragmentA"
        android:name="kim.hsl.nav.FragmentA"
        android:label="fragment_a"
        tools:layout="@layout/fragment_a" >
        <action
            android:id="@+id/action_fragmentA_to_fragmentB"
            app:destination="@id/fragmentB"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim" />
    </fragment>
    <fragment
        android:id="@+id/fragmentB"
        android:name="kim.hsl.nav.FragmentB"
        android:label="fragment_b"
        tools:layout="@layout/fragment_b" >
        <action
            android:id="@+id/action_fragmentB_to_fragmentA"
            app:destination="@id/fragmentA"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim" />

        <!-- 配置完毕后 菜单栏/Build/Make 编译一下,
             自动生成 FragmentBArgs.java 代码, 之后调用该自动生成的类传参 -->
        <argument
            android:name="NAME"
            app:argType="string"
            android:defaultValue="Jerry"/>
        <argument
            android:name="AGE"
            app:argType="integer"
            android:defaultValue="12"/>
    </fragment>
</navigation>

3、页面相关 Kotlin 代码


主要是 Activity 和 Fragment 代码 ;


I、MainActivity 页面代码


这是主页面 , 复杂使用 Navigation 添加 Fragment ;

package kim.hsl.nav

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.Navigation.findNavController
import androidx.navigation.ui.NavigationUI

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // fragmentContainerView 组件的 管理 操作通过 NavController 完成
        // 对应的就是 navController 实例变量
        val navController = findNavController(this, R.id.fragment)
        NavigationUI.setupActionBarWithNavController(this, navController)
    }
}

II、FragmentA 页面代码


FragmentA 跳转到 FragmentB 使用传统的方式传递参数 , 类型不安全 ;


FragmentA 页面代码 :

package kim.hsl.nav

import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.fragment.app.Fragment
import androidx.navigation.Navigation

// 定义 Kotlin 常量
private const val ARG_PARAM_NAME = "NAME"
private const val ARG_PARAM_AGE = "AGE"

class FragmentA : Fragment() {
    private var name: String? = null
    private var age: Int? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        arguments?.let {
            name = it.getString(ARG_PARAM_NAME)
            age = it.getInt(ARG_PARAM_AGE)
        }

        Log.i("TAG", "FragmentB 传递到 FragmentA 的参数为 name = $name , age = $age")
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // 设置 Fragment 布局文件
        return inflater.inflate(R.layout.fragment_a, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val button = view.findViewById<Button>(R.id.button)
        button.setOnClickListener {
            // 正常方式传递参数
            var args: Bundle = Bundle().apply {
                // 设置 Bundle 对象参数数据
                this.putString(ARG_PARAM_NAME, "Tom")
                this.putInt(ARG_PARAM_AGE, 18)
            }

            // 获取 NavigationController
            val navController = Navigation.findNavController(it)
            // 按照 action_fragmentA_to_fragmentB 对应的 action 的导航路线走
            navController.navigate(R.id.action_fragmentA_to_fragmentB, args)
        }
    }
}

III、FragmentB 页面代码


FragmentB 跳转到 FragmentA 使用安全方式传递参数 ;


FragmentB 页面代码 :

package kim.hsl.nav

import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.navigation.Navigation

// 定义 Kotlin 常量
private const val ARG_PARAM_NAME = "NAME"
private const val ARG_PARAM_AGE = "AGE"

class FragmentB : Fragment() {
    private var name: String? = null
    private var age: Int? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            name = it.getString(ARG_PARAM_NAME)
            age = it.getInt(ARG_PARAM_AGE)
        }

        Log.i("TAG", "FragmentA 传递到 FragmentB 的参数为 name = $name , age = $age")
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // 设置 Fragment 布局文件
        return inflater.inflate(R.layout.fragment_b, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val button = view.findViewById<Button>(R.id.button)
        button.setOnClickListener {
            var args: Bundle = FragmentBArgs.Builder()
                                            .setNAME("Trump")
                                            .setAGE(80)
                                            .build().toBundle()

            // 获取 NavigationController
            val navController = Navigation.findNavController(it)
            // 按照 action_fragmentB_to_fragmentA 对应的 action 的导航路线走
            navController.navigate(R.id.action_fragmentB_to_fragmentA, args)
        }
    }
}

4、执行结果


编译运行程序 , 进入默认 Launcher 界面 , 默认显示 FragmentA 页面 ,

在这里插入图片描述

点击 " 跳转到 B " 按钮 , 此时跳转到了 FragmentB , 使用传统方式传递的参数也能正常获取 ,

kim.hsl.nav I/TAG: FragmentA 传递到 FragmentB 的参数为 name = Tom , age = 18

在这里插入图片描述

在 FragmentB 页面点击 " 跳转到 A " 按钮 , 使用安全方式传递的参数 , 也能正常打印出来 ;

在这里插入图片描述


代码地址 :