前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Jetpack】Navigation 导航组件 ④ ( Fragment 跳转中使用 safe args 安全传递参数 )

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

作者头像
韩曙亮
发布2023-10-11 18:31:23
5190
发布2023-10-11 18:31:23
举报
文章被收录于专栏:韩曙亮的移动开发专栏
代码地址 :

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

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

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

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

代码语言:javascript
复制
public open fun navigate(@IdRes resId: Int)

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

代码语言:javascript
复制
public open fun navigate(@IdRes resId: Int, args: Bundle?)
2、传统数据传递实现步骤

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

代码语言:javascript
复制
// 定义 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 ;

代码语言:javascript
复制
// 获取 NavigationController
val navController = Navigation.findNavController(it)
// 按照 action_fragmentA_to_fragmentB 对应的 action 的导航路线走
navController.navigate(R.id.action_fragmentA_to_fragmentB, args)

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

代码语言:javascript
复制
// 按照 action_fragmentA_to_fragmentB 对应的 action 的导航路线走
navController.navigate(R.id.action_fragmentA_to_fragmentB, args)

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

代码语言:javascript
复制
// 定义 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 完整代码示例 :

代码语言:javascript
复制
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 完整代码示例 :

代码语言:javascript
复制
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 日志面板 , 输出如下内容 :

代码语言:javascript
复制
kim.hsl.nav I/TAG: FragmentA 传递到 FragmentB 的参数为 name = Tom , age = 18

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

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

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

代码语言:javascript
复制
// 正常方式传递参数
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 类型的 ,

代码语言:javascript
复制
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 代码块完全注释掉 ;

代码语言:javascript
复制
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 配置的完整源码 ;

配置如下 :

代码语言:javascript
复制
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 依赖 ;

代码语言:javascript
复制
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'androidx.navigation.safeargs'
}

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

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

参数格式为 :

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

完整的参数配置如下 :

代码语言:javascript
复制
    <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 代码如下 : ( 仅做参考 )

代码语言:javascript
复制
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 类型对象 ;

代码语言:javascript
复制
            var args: Bundle = FragmentBArgs.Builder()
                                            .setNAME("Trump")
                                            .setAGE(80)
                                            .build().toBundle()

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

代码语言:javascript
复制
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 对象即可 ;

代码语言:javascript
复制
        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 构建脚本代码示例 :

代码语言:javascript
复制
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 构建脚本 :

代码语言:javascript
复制
// 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 构建脚本 :

代码语言:javascript
复制
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 页面布局 :

代码语言:javascript
复制
<?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 页面布局 :

代码语言:javascript
复制
<?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 页面布局 :

代码语言:javascript
复制
<?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 配置 :

代码语言:javascript
复制
<?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 ;

代码语言:javascript
复制
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 页面代码 :

代码语言:javascript
复制
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 页面代码 :

代码语言:javascript
复制
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 , 使用传统方式传递的参数也能正常获取 ,

代码语言:javascript
复制
kim.hsl.nav I/TAG: FragmentA 传递到 FragmentB 的参数为 name = Tom , age = 18

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

代码地址 :

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-08-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 代码地址 :
  • 一、页面跳转间的传统的数据传递方式
    • 1、传统的数据传递方式 - Bundle 传递数据
      • 1、Navigation 组件中的 Bundle 数据传递
      • 2、传统数据传递实现步骤
      • 3、FragmentA 完整代码示例
      • 4、FragmentB 完整代码示例
      • 5、执行结果
    • 2、使用 Bundle 传递数据安全性差
    • 二、页面跳转间的传统的数据传递方式
      • 1、导入插件依赖
        • 2、使用插件
          • 3、在 navigation_graph.xml 中定义要传递的 argument 参数信息
            • 4、重新编译生成参数传递相关代码
              • 5、调用 FragmentBArgs 类生成参数 Bundle
                • 6、FragmentA 中获取参数
                • 三、两种传参方式的完整代码示例
                  • 1、Gradle 构建脚本
                    • I、根目录下 settings.gradle 构建脚本
                    • II、根目录下 build.gradle 构建脚本
                    • III、Module 目录下 build.gradle 构建脚本
                  • 2、res 资源配置
                    • I、MainActivity 页面布局
                    • II、FragmentA 页面布局
                    • III、FragmentB 页面布局
                    • IV、navigation_graph.xml 配置
                  • 3、页面相关 Kotlin 代码
                    • I、MainActivity 页面代码
                    • II、FragmentA 页面代码
                    • III、FragmentB 页面代码
                  • 4、执行结果
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档