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

这篇具有很好参考价值的文章主要介绍了【Jetpack】Navigation 导航组件 ④ ( Fragment 跳转中使用 safe args 安全传递参数 )。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


代码地址 :文章来源地址https://www.toymoban.com/news/detail-672411.html

  • 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 界面 ,

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

点击 " 跳转到 B " , 此时 , 跳转到 FragmentB 界面 :
【Jetpack】Navigation 导航组件 ④ ( Fragment 跳转中使用 safe args 安全传递参数 ),# Jetpack,Android,Jetpack,Navigation,Kotlin,Gradle

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

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

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


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”) 也不会报错 ;

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

上面的 使用 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
}

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

在 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 " 目录下 ,

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


生成的 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 页面 ,

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

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

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

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

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

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


代码地址 :

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

到了这里,关于【Jetpack】Navigation 导航组件 ④ ( Fragment 跳转中使用 safe args 安全传递参数 )的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android:单Activity多Fragment,Navigation实现Fragment跳转,Fragment之间通过ViewModel共享数据

    1、activity_main.xml 2、MainActivity FragmentA:包括SeekBar和一个按钮,点击button跳转到FragmentB FragmentB:SeekBar加一和减一操作的按钮,一个返回FragmentA的按钮,即经过加减操作以后,在FragmentA上显示加减的结果。 nav_graph.xml 1、fragment_home.xml 2、HomeFragment 1、fragment_detail.xml 2、DetailFragm

    2023年04月08日
    浏览(30)
  • 利用Jetpack Compose进行导航(Navigation)

    Jetpack Compose是一个现代化的,声明式的UI工具包,它让我们可以更快、更简便地构建Android的界面。今天,我们要讨论如何使用Jetpack Compose和它的导航库(Navigation Compose)来进行应用导航。 Navigation Compose是一个用于管理Compose界面中的导航的库,它不仅提供了丰富的API以支持不

    2024年02月12日
    浏览(31)
  • Android笔记(十):结合Navigation组件实现Compose界面的导航

    在Android笔记(七)搭建Android JetPack Compose组件中Scaffold脚手架 一文中通过定义一个导航的函数来实现不同界面的切换。如果没有传递任何参数,这样的导航处理也是可以接受的,处理方式也非常简单。但是,如果考虑到不同Compose界面的切换且传递参数,或者有更复杂地处理情

    2024年01月22日
    浏览(42)
  • React Navigation 使用导航

    在 Web 浏览器中,您可以使用锚标记链接到不同的页面。当用户单击链接时,URL 会被推送到浏览器 历史记录堆栈 中。当用户按下后退按钮时,浏览器会从历史堆栈顶部弹出该项目,因此活动页面现在是以前访问过的页面。React Native 不像 Web 浏览器那样具有全局历史堆栈的内

    2024年02月11日
    浏览(42)
  • Harmony 个人中心(页面交互、跳转、导航、容器组件)

      今天是1024,祝各位程序员们,钱多事少离家近,不秃也强bug黄。在上一篇文章中,我们了解了DevEco Studio的主推开发语言 ArkTS ,并写了一个简单的例子,本文我们将学习另外一个例子来加深我们对于鸿蒙应用开发的理解。   本文的例子同样来源于HarmonyOS学堂,根据源码

    2024年02月06日
    浏览(25)
  • Android Jetpack学习系列——Navigation

    写在前面 Google在2018年就推出了Jetpack组件库,但是直到今天我才给重视起来,这真的不得不说是一件让人遗憾的事。过去几年的空闲时间里,我一直在尝试做一套自己的组件库,帮助自己快速开发,虽然也听说过Jetpack,但是压根儿也没去了解,但是其实自己已经无形之中用到

    2024年02月02日
    浏览(31)
  • RN 使用react-navigation写可以滚动的横向导航条(expo项目)

      装包:             参考链接: https://chat.xutongbao.top/ https://www.cnblogs.com/tengyuxin/p/13263143.html https://reactnavigation.org/docs/material-top-tab-navigator/ 

    2024年02月13日
    浏览(45)
  • 微信小程序页面的跳转和导航的配置和vant组件

    结论: navigateTo ,  redirectTo  只能打开非 tabBar 页面。 switchTab  只能打开 tabBar 页面。 reLaunch  可以打开任意页面。 页面底部的 tabBar 由页面决定,即只要是定义为 tabBar 的页面,底部都有 tabBar。 调用页面路由带的参数可以在目标页面的 onLoad 中获取。 (1)当我们使用 redirectTo跳

    2024年02月09日
    浏览(41)
  • 【见微知著】Android Jetpack - Navigation的架构设计

    前言:人总是理所当然的忘记,是谁风里雨里,一直默默的守护在原地。 Navigation 作为 Android Jetpack 组件库中的一员,是一个通用的页面导航框架。为 单 Activity 架构而生的端内路由导航,用来管理 Fragment 的切换,并且可以通过可视化的方式,看见 App 的交互流程。今天主要来

    2024年02月08日
    浏览(41)
  • 【Unity】AI-Navigation导航系统生成导航网络

    在Unity资源包中添加 AI Navigation 插件,并 Install 。 找到 AN 标志,勾选 Show Only Selected(可有可无,只是为了更好地看出来网格在哪)。 在project面板里面创建 AI-NavMesh Surface 文件。 双击新建的文件,点击 Back 烘焙网格。 勾选 Show NavMesh 显示效果,识别为是否是障碍物的依据,就

    2024年04月27日
    浏览(28)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包