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

2023-08-25 13:48:36


代码地址 :





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




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 代码, 之后调用该自动生成的类 进行 传参 ;


生成的类在 " Navigation\app\build\generated\source\navigation-args\debug\kim\hsl\nav " 目录下 ,

在这里插入图片描述


生成的 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 " 按钮 , 使用安全方式传递的参数 , 也能正常打印出来 ;

在这里插入图片描述


代码地址 :

更多推荐

DevOps&Apipost

DevOps旨在通过自动化流程和改善协作,实现软件开发、测试和交付的一体化,从而提高软件交付的质量和速度。为了提高工作效率,加快软件的交付流程,越来越多企业的选择DevOps工作流程。其中API管理的地位非常重要。随着API数量的大幅增长,也带来了新的API管理需求。如何在DevOps工作流中进行API全生命周期管理,

STM32WB55开发(1)----监测STM32WB连接状态

STM32WB55开发----1.监测STM32WB连接状态概述硬件准备视频教学样品申请完整代码下载选择芯片型号配置时钟源配置时钟树RTC时钟配置查看开启STM32_WPAN条件配置HSEM配置IPCC配置RTC启动RF开启蓝牙LED配置设置工程信息工程文件设置参考文档SVCCTL_App_Notification结果

STM32WB55开发(5)----调整射频功率

STM32WB55开发----5.调整射频功率概述硬件准备视频教学样品申请源码下载选择芯片型号配置时钟源配置时钟树RTC时钟配置查看开启STM32_WPAN条件配置HSEM配置IPCC配置RTC启动RF开启蓝牙开启串口调试配置蓝牙参数设置工程信息工程文件设置Keil工程配置代码配置射频功率ACI_HAL_SET_TX_

在Kubernetes上安装和配置Istio:逐步指南,展示如何在Kubernetes集群中安装和配置Istio服务网格

🌷🍁博主猫头虎带您GotoNewWorld.✨🍁🦄博客首页——猫头虎的博客🎐🐳《面试题大全专栏》文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺🌊《IDEA开发秘籍专栏》学会IDEA常用操作,工作效率翻倍~💐🌊《100天精通Golang(基础入门篇)》学会Golang语言,畅玩云原生,走遍大

基于 STM32自研多任务+SpringBoot+Vue 农业大棚智能调光系统

工作以后常常容易感到疲于奔命,即使在周末也没有得到高质量的休息。打工人/学生党如何过周末?你有哪些延长周末和下班时间的好方法吗?-方法就是多积累,多发博客,将感悟全写出来!!,接下来我给大家展示一个课程设计:源码和硬件端代码就不与展示了,在该博客的资源绑定中,大家如果有兴趣可以参考一下。前后端代码保存在我的资源中我的主

STM32 cubemx配置USART DMA传输

文章目录前言一、DMA概念二、STM32DMA数据手册分析3.DMA模式介绍4.cubemx配置总结前言本篇文章来讲解DMA的概念,并使用DMA来进行串口的数据收发。一、DMA概念DMA(DirectMemoryAccess,直接内存访问)是一种计算机系统的技术,允许外部设备(如硬盘驱动器、网络适配器或图形卡)直接与计

大数据与人工智能的未来已来

大数据与人工智能的定义大数据:大数据指的是规模庞大、复杂性高、多样性丰富的数据集合。这些数据通常无法通过传统的数据库管理工具来捕获、存储、管理和处理。大数据的特点包括"3V":大量(Volume):大数据集合包含大量的数据,通常是以TB(千兆字节)或PB(百万兆字节)为单位。多样性(Variety):大数据包括各种类型

【无标题】

更多技术交流、求职机会,欢迎关注字节跳动数据平台微信公众号,回复【1】进入官方交流群背景介绍Notebook解决的问题部分任务类型(python、spark等)在创建配置阶段,需要进行分步调试;由于探索查询能力较弱,部分用户只能通过其他平台or其他途径进行开发调试,但部署到Dorado时,又发现行为不一致等问题(运行环

服务器管理

腾讯云服务器相关管理linux下安装python3linux自带2.x,有时候需要2.x执行一些工具,开发的时候又想用p3,就需要同时装python2和python3依次执行以下命令sshxxx@xx.xx.xx.xx#进入linux服务器su#输入密码,如果不知道管理员账户但拥有sudo权限,下面所有命令前缀都跟su

Kafka消息发送可靠性分析

ApacheKafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者和生产者之间的所有实时数据。Kafka的主要特性包括:高吞吐量、可扩展性、持久性、分布式、可容错等。这些特性使得Kafka成为大规模数据处理和实时数据分析的理想选择。然而,关于Kafka的一个常见问题是其消息发送的可靠性。下面我们将详细分析K

504 错误码排查

当出现504错误码时,表示请求超时,服务器无法及时响应请求,需要检查下应用是否有什么耗时的操作,比如是否出现了SQL慢查询、是否接口发生死循环、是否出现死锁等,同时需要关注服务器系统负载高不高。网络异常接口原本好好的,突然出现超时,最常见的原因可能是网络出现异常,比如:偶然的网络抖动,或者是带宽被占满了。网络抖动:大多

热文推荐