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



    代码地址 :





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




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



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


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

    public open fun navigate(@IdRes resId: Int)
    
    • 1

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

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

    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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

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

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

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

    最后 , 在跳转后的界面中 , 调用 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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    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)
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    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)
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    5、执行结果


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

    在这里插入图片描述

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

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

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

    在这里插入图片描述


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


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

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

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

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

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

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

    arguments?.let {
        name = it.getString("NAME")
    }
    
    • 1
    • 2
    • 3

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

    下面的代码中 , 调用 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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    在 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
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    2、使用插件


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

    plugins {
        id 'com.android.application'
        id 'org.jetbrains.kotlin.android'
        id 'androidx.navigation.safeargs'
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

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


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


    参数格式为 :

            <argument
                android:name="NAME"
                app:argType="string"
                android:defaultValue="Jerry"/>
    
    • 1
    • 2
    • 3
    • 4
    • 参数名称为 " 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" />
    
            
            <argument
                android:name="NAME"
                app:argType="string"
                android:defaultValue="Jerry"/>
            <argument
                android:name="AGE"
                app:argType="integer"
                android:defaultValue="12"/>
        fragment>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    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");
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160

    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()
    
    • 1
    • 2
    • 3
    • 4

    创建完 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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    后续章节可以查看 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
    • 2
    • 3
    • 4
    • 5
    • 6




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




    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'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    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
    }*/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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'
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    2、res 资源配置


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


    I、MainActivity 页面布局


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


    MainActivity 页面布局 :

    
    <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    II、FragmentA 页面布局


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


    FragmentA 页面布局 :

    
    <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">
    
        
        <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    III、FragmentB 页面布局


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


    FragmentB 页面布局 :

    
    <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">
    
        
        <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    IV、navigation_graph.xml 配置


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

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


    navigation_graph.xml 配置 :

    
    <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" />
    
            
            <argument
                android:name="NAME"
                app:argType="string"
                android:defaultValue="Jerry"/>
            <argument
                android:name="AGE"
                app:argType="integer"
                android:defaultValue="12"/>
        fragment>
    navigation>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    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)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    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)
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    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)
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    4、执行结果


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

    在这里插入图片描述

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

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

    在这里插入图片描述

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

    在这里插入图片描述


    代码地址 :

  • 相关阅读:
    企业工程项目管理系统源码(三控:进度组织、质量安全、预算资金成本、二平台:招采、设计管理)
    Linux 文件目录指令(常见)
    软件需求怎么写?
    火山引擎云原生存储加速实践
    【侯捷C++面向对象高级编程】(下)
    Linux企业应用——Docker(三)之Docker仓库、Docker hub官方仓库的使用
    Android---底部弹窗之BottomSheetDialog
    【剑指Offer】31.栈的压入、弹出序列
    Selenium UI 自动化
    LINQ to SQL语句之ADO.NET与LINQ to SQL
  • 原文地址:https://blog.csdn.net/han1202012/article/details/131406972