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

news2025/1/16 6:45:41

文章目录

  • 一、页面跳转间的传统的数据传递方式
    • 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、执行结果


代码地址 :

  • CSDN ( 本博客代码快照 | 推荐下载 0 积分 ) : https://download.csdn.net/download/han1202012/88251933
  • GitHub ( 可能已经覆盖 ) : https://github.com/han1202012/Navigation




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




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

在这里插入图片描述


代码地址 :

  • CSDN ( 本博客代码快照 | 推荐下载 0 积分 ) : https://download.csdn.net/download/han1202012/88251933
  • GitHub ( 可能已经覆盖 ) : https://github.com/han1202012/Navigation

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/926751.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux详解(包含Linux安装教程)

文章目录 Linux详解一、安装Linux操作系统VMware介绍安装虚拟机VMware下载centos 7系统安装centos 7系统 二、Linux基础命令Linux的目录结构Linux命令入门目录切换相关命令 cd、pwd相对路径、绝对路径和特殊路径符掌握通过mkdir命令创建文件夹文件操作命令touch、cat、more文件…

APT80DQ60BG-ASEMI新能源功率器件APT80DQ60BG

编辑&#xff1a;ll APT80DQ60BG-ASEMI新能源功率器件APT80DQ60BG 型号&#xff1a;APT80DQ60BG 品牌&#xff1a;ASEMI 芯片个数&#xff1a;2 封装&#xff1a;TO-3P 恢复时间&#xff1a;&#xff1e;50ns 工作温度&#xff1a;-55C~150C 浪涌电流&#xff1a;600A …

Python“牵手”易贝(Ebay)商品列表数据,关键词搜索ebayAPI接口数据,ebayAPI接口申请指南

Ebay平台API接口是为开发电商类应用程序而设计的一套完整的、跨浏览器、跨平台的接口规范&#xff0c; EbayAPI接口是指通过编程的方式&#xff0c;让开发者能够通过HTTP协议直接访问Ebay平台的数据&#xff0c;包括商品信息、店铺信息、物流信息等&#xff0c;从而实现Ebay平…

Etsy如何安全养店?7个因素你要知道

Etsy是全球大型的创意市场电商平台&#xff0c;很多跨境玩家在开店之后&#xff0c;兴致冲冲开始上架&#xff0c;结果流量没有不说&#xff0c;很快店铺就被封禁。注意了&#xff01;Etsy也是一个规则比较严格的平台&#xff0c;想要做好Etsy&#xff0c;一定要看好下面这7个因…

【Java 动态数据统计图】前后端对接数据格式(Map返回数组格式数据)六(120)

说明&#xff1a; 前端使用&#xff1a;vue3.0 前后端对接数据格式&#xff1a;无非就是前端把后端返回的数据处理为自己想要的格式&#xff0c;或者&#xff0c;后端给前端处理好想要的格式&#xff1b; 针对前后端的柱状图&#xff0c;趋势图等数据对接&#xff0c;前端一般需…

Java网络编程(二)经典案例[粘包拆包]

粘包拆包 概述 TCP是面向流的协议,TCP在网络上传输的数据就是一连串的数据,完全没有分界线。 TCP协议的底层并不了解上层业务的具体定义,它会根据TCP缓冲区的实际情况进行包的划分。 在业务层面认为一个完整的包可能会被TCP拆分成多个小包进行发送,也可能把多个小的包封装成一…

Qt(C++)计算一段程序执行经过的时间

一、前言 在许多应用程序和系统中,需要对经过的时间进行计算和记录。例如 可能想要测量某个操作的执行时间,或者记录一个过程中经过的时间以进行性能分析。在这些场景下,准确地计时是非常重要的。 Qt提供了一个功能强大的计时器类QElapsedTimer,可以方便地记录经过的时间…

SSL核心概念 SSL类型级别

SSL&#xff1a;SSL&#xff08;Secure Sockets Layer&#xff09;即安全套接层&#xff0c;及其继任者传输层安全&#xff08;Transport Layer Security&#xff0c;TLS&#xff09;是为网络通信提供安全及数据完整性的一种安全协议。TLS与SSL在传输层对网络连接进行加密。 H…

b站文件太大无法上传怎么办?视频压缩这样做

据了解&#xff0c;B站在网页端和桌面客户端允许上传的视频上线为4G&#xff0c;当视频文件超出这个大小时&#xff0c;我们就要考虑才去方法压缩一下视频大小&#xff0c;不然就会出现无法上传的问题&#xff0c;下面就给大家分享几个视频压缩方法&#xff0c;供大家参考使用。…

sql server删除历史数据

1 函数 datediff函数: DATEDIFF ( datepart , startdate , enddate )datepart的取值可以是year,quarter,Month,dayofyear,Day,Week,Hour,minute,second,millisecond startdate 是从 enddate 减去。如果 startdate 比 enddate 晚&#xff0c;返回负值。 2 例子 删除2023年以…

消息队列的模拟实现(一)

消息队列的模拟实现&#xff08;一&#xff09; 认识消息队列生产者消费者模型两大特征市面上可见的消息队列MQ消息队列的特点&#xff1a; 模拟实现消息队列模型分类提供的核心API消息队列的推拉模式 交换机的类型持久化网络通信额外提供的方法使用一个TCP和信道之间的区别 消…

计算机网络-笔记-第一章-计算机网络概述

目录 一、第一章——计算机网络概述 1、因特网概述 &#xff08;1&#xff09;网络、互联网、因特网 &#xff08;2&#xff09;因特网发展的三个阶段 &#xff08;3&#xff09;因特网服务的提供者&#xff08;ISP&#xff09; &#xff08;4&#xff09;因特网标准化工…

源代码加密、防泄密软件

企业源代码防泄密是指企业采取措施保护其软件或应用程序源代码不被未授权的人员获取、泄露或盗用的一种安全措施。源代码是软件的核心组成部分&#xff0c;其中包含了程序员编写的具体指令和算法&#xff0c;可以被计算机理解和执行。泄漏企业的源代码可能导致严重的后果&#…

DEIF SCM-1测量模块

参数测量&#xff1a; SCM-1测量模块通常用于测量电力系统的各种参数&#xff0c;例如电压、电流、频率、功率因数等。 监测功能&#xff1a; 它能够实时监测电力系统的性能&#xff0c;以确保其在正常运行范围内。 通信接口&#xff1a; DEIF的测量模块通常具有通信接口&…

批量随机改名并自定义长度,让文件夹命名更随心

大家好&#xff01;你是否曾经为批量改名文件夹而苦恼过&#xff1f;现在&#xff0c;我们为你带来了解决方案&#xff01;我们的工具可以帮助你轻松批量给文件夹进行随机改名&#xff0c;并且还可以自定义文件夹名的长度&#xff0c;让你的文件夹命名更加随心和个性化。 首先第…

内存管理:TLSF算法原理分析

1、动态内存分配DSA&#xff1a; 动态内存分配&#xff08;DSA&#xff09;在计算机中十分重要&#xff0c;其主要用于在程序运行时&#xff0c;根据需要分配和释放内存。 (1)、DSA的几个要点分别为&#xff1a; 内存管理方式&#xff1a;动态内存分配与静态内存分配 相对应&…

JMeter性能测试(上)

一、基础简介 界面 打开方式 双击 jmeter.bat双击 ApacheJMeter.jsr命令行输入 java -jar ApacheJMeter.jar 目录 BIN 目录&#xff1a;存放可执行文件和配置文件 docs目录&#xff1a;api文档&#xff0c;用于开发扩展组件 printable-docs目录&#xff1a;用户帮助手册 li…

在线流程图软件哪个好?5款打工人必备的效率神器!

​流程图是可视化工具的一种&#xff0c;被广泛用于呈现和理解复杂的流程和工作流程。本篇文章我们将向你介绍5款优秀的在线流程图软件&#xff0c;助你提升工作效率&#xff0c;它们分别是&#xff1a;boardmix、Lucidchart、draw.io、Creately、Coggle。 在选择在线流程图软…

对numpy以及pandas中axis的理解

用线代的概念来理解轴&#xff0c;也就是dimension 在numpy中&#xff0c;最小的一维数组就可以看做是一个行列式&#xff0c;通常一个行列式写作如下形式 在numpy中就是这样的形式 anp.arange(4) #array([0, 1, 2, 3]) 对一个二维的矩阵&#xff0c;通常可以由两个行列式组…

用AI重构的钉钉,“钱”路在何方?

点击关注 文&#xff5c;郝 鑫&#xff0c;编&#xff5c;刘雨琦 钉钉2023年生态大会&#xff0c;离开了两年的无招&#xff0c;遇到了单飞9天的钉钉。 “做小钉钉、做好钉钉、做酷钉钉”&#xff0c;无招重申了钉钉的方向。 无招提到的三点&#xff0c;再加上“高质量增长”…