Navagation导航使用以及踩坑解决方案

哎呀,之前一段时间都在忙,难得周日放假就继续写下吧,因为之前需要技术分享,所以就研究下了Jetpack的组件Navgation导航,这个组件的功能很强大,而且实用性感觉很强,打算下一步去重构项目的时候就使用他进行重构,他可以做到什么呢?官方的文档给出了答案:
官方地址:
https://developer.android.com/guide/navigation
导航组件提供各种其他优势,包括以下内容:

  • 处理 Fragment 事务。
  • 默认情况下,正确处理往返操作。
  • 为动画和转换提供标准化资源。
  • 实现和处理深层链接。
  • 包括导航界面模式(例如抽屉式导航栏和底部导航),用户只需完成极少的额外工作。
  • Safe Args - 可在目标之间导航和传递数据时提供类型安全的 Gradle 插件。
  • ViewModel 支持 - 您可以将 ViewModel 的范围限定为导航图,以在图表的目标之间共享与界面相关的数据
    然后说下怎么使用吧
    请在你项目中的project/app/src/main/src的目录下新建Android Resource File文件:如图
    截屏2020-12-13 上午9.15.49.png

    然后你就会在res/navation的文件夹中看到你新建的文件
    截屏2020-12-13 上午9.16.45.png

    截屏2020-12-13 上午9.18.56.png

    进入刚刚我们创建的resourceFile后,就可以在左上角看到创建按钮,然后,在看到下面的这些Fragment都是我提前创建好的
    截屏2020-12-13 上午9.20.55.png

看,这是我这边提前写好的布局,是不是从右图就可以清晰的看到我这个页面可以前往哪些页面,并知道与哪些页面产生了关联?
然后我就继续说下这些是如何做到的:
在我们点击右上角的Design,你就可以看到下面这些属性:


截屏2020-12-13 上午9.23.59.png

先从Arguments开始说起:


截屏2020-12-13 上午9.24.52.png

这个是启动Fragment的时候携带的参数,Name参数的名字,Type参数的类型,下面是数组和可空,然后Default Value为默认参数

然后是Actions,这个就比较重要了,他是决定着这个页面可以与哪些页面产生关联的


截屏2020-12-13 上午9.28.15.png

ID:是后面执行跳转的时候,需要用到的重要属性,这个会告诉navControlln需要执行哪个页面到哪个页面的跳转
From:告诉navControlln,是从哪个页面出发
Destination:从From的这个页面到达哪个页面
然后下面都是些场景转换和动画效果实现的属性

DeepLinks:是深度链接的
这个深度链接就比较有意思了,在外部引用拉起我们app的某个页面的时候就可以用上了,但是他并不是直接达到这个页面的,而是把需要通往这个页面的路径上的页面全部都拉起,才会产生直接达到这个页面的效果,举例吧:
我在某个网页上产生了点击事件,而这个点击事件会拉起我们应用的D页面,但是在打开D页面之前需要打开ABC三个页面,而通过深度链接去打开D页面的时候,前面ABC三个页面也会被拉起

当然,我们不单单是在这里进行各种设置,页面通过直接拉动的方式,在布局中调整的,因为我不会上传git动画,所以各位看官可以自己试试

在这个过程中,如何体现Navigation方便管理Fragment的呢?毕竟前面都是说布局的属性怎么用而已,是不是?但是对我的理解来说,单单从布局中就可以清晰看到了每个页面是如何让关联的,就已经很舒服了(毕竟有时候看项目的时候找页面真的很蛋疼,尤其是新接触项目的时候)

因为navigation很多人都写了,所以我把大佬们写得比较好而且清晰的文章也附上吧,我就不再进行过多的重复说明了:
《Android导航组件Navigation从入门到精通》
https://blog.csdn.net/yingaizhu/article/details/105972720

后面我继续说我在项目中实际运用中遇到的问题,还有解决方法:
1 配置问题:因为在配置navigation的时候发现会和当前的gradle插件版本发生冲突问题:
解决办法:
方案1:把gradle版本升级到最高版本,这样就可以直接使用,但是谨慎升级gradle插件版本,因为项目中可能会有其他组件使用冲突问题
方案2:在项目中build.gradle中的dependencies中修改gralde插件的版本为:
classpath 'com.android.tools.build:gradle:3.5.0'
并且同步在project-gradle-wrapper-gradle-wrapper.properties中修改distributionUrl为
distributionUrl=https://services.gradle.org/distributions/gradle-5.4.1-all.zip
这样就可以正常使用navigation了

2 出现无法找到导航图的问题:
解决方案:
1 检查在xml文件中,是否设置了navGraph的资源文件
2 如果设置了还是无法找到,请点击右上角的扳手的图标


截屏2020-12-23 下午5.10.00.png

3 使用组件自带fragmentNavigator跳转时候,出现重复新建fragment对象问题:
解决方案:请自定义fragmentNavigator,自定义方法如下:
1 自定义的fragmentNavigator:

import android.content.Context
import android.os.Bundle
import android.util.Log
import androidx.annotation.IdRes
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.navigation.NavDestination
import androidx.navigation.NavOptions
import androidx.navigation.Navigator
import androidx.navigation.fragment.FragmentNavigator
import java.util.ArrayDeque

@Navigator.Name("fix_fragment")
class FixFragmentNavigator(private val context: Context,
                           private val mManager: FragmentManager,
                           private val containerId:Int) : FragmentNavigator(context,mManager,containerId) {

    override fun navigate(destination: Destination, args: Bundle?, navOptions: NavOptions?, navigatorExtras: Navigator.Extras?): NavDestination? {
        if (mManager.isStateSaved){
            Log.e("FixFragmentNavigation", "Ignoring navigate() call : FragmentManager has already" +
                    "saved its state")
            return null
        }
       var className=destination.className
        if (className[0]=='.'){
            className=context.packageName+className
        }
        val fragmentTransaction=mManager.beginTransaction()
        var enterAnim=navOptions?.enterAnim ?:-1
        var exitAnim=navOptions?.exitAnim ?:-1
        var popEnterAnim=navOptions?.popEnterAnim ?:-1
        var popExitAnim=navOptions?.popExitAnim ?:-1
        if (enterAnim!=-1||exitAnim!=-1||popEnterAnim!=-1||popExitAnim!=-1){
            enterAnim=if (enterAnim !=-1) enterAnim else 0
            exitAnim=if (exitAnim!=-1) exitAnim else 0
            popEnterAnim=if (popEnterAnim!=-1) popEnterAnim else 0
            popExitAnim=if (popExitAnim!=-1) popExitAnim else 0
        }
        //当前正在显示的fragment
        val fragment=mManager.primaryNavigationFragment
        if (fragment!=null){
            //隐藏正在显示的页面,准备显示新的洁面
            fragmentTransaction.hide(fragment)
        }
        var frag:Fragment?
        val tag=destination.id.toString()
        //获取要显示的fragment
        frag=mManager.findFragmentByTag(tag)
        if (frag!=null){
            fragmentTransaction.show(frag)
        }else{
            //如果为null,则创建
            frag=instantiateFragment(context,mManager,className,args)
            frag.arguments=args
            fragmentTransaction.add(containerId,frag,tag)
        }
        fragmentTransaction.setPrimaryNavigationFragment(frag)

        @IdRes val destId=destination.id
        //mBackStack 是私有的,这里无法获取,需要通过反射获取
        //反射获取字段,并且获取值
        val field=FragmentNavigator::class.java.getDeclaredField("mBackStack")
        field.isAccessible=true
        val mBackStack:java.util.ArrayDeque<Int> =field.get(this) as ArrayDeque<Int>

        val initialNavigation=mBackStack.isEmpty()

        val isSingleToReplacement=(navOptions !=null&&!initialNavigation
                &&navOptions.shouldLaunchSingleTop()
                && mBackStack.peekLast() == destId)

        val isAdded:Boolean
        isAdded=if (initialNavigation){
            true
        }else if (isSingleToReplacement){
            if (mBackStack.size>1){
                mManager.popBackStack(
                        generateBackStackName(mBackStack.size,mBackStack.peekLast()!!),
                        FragmentManager.POP_BACK_STACK_INCLUSIVE
                )
                fragmentTransaction.addToBackStack(generateBackStackName(mBackStack.size,destId))
            }
            false
        }else{
            fragmentTransaction.addToBackStack(generateBackStackName(mBackStack.size+1,destId))
            true
        }
        if (navigatorExtras is Extras){
            for ((key,value) in navigatorExtras.sharedElements){
                fragmentTransaction.addSharedElement(key!!,value!!)
            }
        }
        fragmentTransaction.setReorderingAllowed(true)
        fragmentTransaction.commit()
        return if (isAdded){
            mBackStack.add(destId)
            destination
        }else{
            null
        }
    }
    private fun generateBackStackName(backStackIndex:Int,destid:Int):String{
        return "$backStackIndex-$destid"
    }
}

2 请在使用navigation组件的容器的activity中的onCreat方法中做如下操作:

  setContentView(R.layout.activity_setting_navhost)
        // 创建navController
        val navController = Navigation.findNavController(this, R.id.fragment_main)
        val navHostFragment = supportFragmentManager.findFragmentById(R.id.fragment_main)!!
        // 生成自定义Navigator对象
        val navigator = FixFragmentNavigator(
                this,
                navHostFragment.childFragmentManager,
                R.id.fragment_main
        )
        // 添加 key, value
        navController.navigatorProvider.addNavigator("fix_fragment", navigator)
        // 要在 CustomNavigator 类被加载之后添加graph,不然找不到 fix_fragment节点
        navController.setGraph(R.navigation.nav_setting, bundleOf(FRAGMENT_DATA_IS_SET to isSetPassword))

3 在导航图中把<fragment>标签修改为<fix_fragment>

4 把承载navigation导航图的<fragment>中的navGraph属性删除,否则会继续使用原本的FragmentNavigator
这样,就可以把我们的自定义的FragmentNavigator进行使用了

4 传参数问题:
这个就很蛋疼了,因为我是在重构项目中开始使用的,但是,在别的页面开始进行navigation的使用的时候,需要把上一个页面的参数传进来,所以我会优先考虑使用FragmentManager的进行传递(因为这不是navigation的范围了),但是有个很重要的因素,就是他们使用的FragmentManager不是同一个,所以会导致数据传递失败了!!!
解决方法:
1 把上一个页面的参数,传到承载navigation容器的Activity/Fragment中,然后然后通过以下方式,把数据传入到起始目的地

       navController.setGraph(R.navigation.nav_setting, bundleOf(FRAGMENT_DATA_IS_SET to isSetPassword))

2 fragment之间传递参数
发送数据:

val bundle = bundleOf("amount" to amount)
view.findNavController().navigate(R.id.confirmationAction, bundle)

接受数据:

val tv = view.findViewById<TextView>(R.id.textViewAmount)
tv.text = arguments?.getString("amount")

3 fragment向activity传递参数
发送数据同上

接受数据:

val tv = view.findViewById<TextView>(R.id.textViewAmount)
tv.text = intent?.getString("amount")

4 如果Fragment中的参数需要用到其他页面,但并是不导航图的下一个目的地,请使用FragmentManager进行数据传递:
同级Fragment中:
发送数据:

 val result=Bundle()
                    result.putString("account",editAccount.text.toString())
                    result.putString("password",editPasswordAgain.text.toString())
                    setFragmentResult("numberKey",result)

接受数据:

setFragmentResultListener("numberKey"){ key, bundle ->
            registerAccount=bundle.getString("account","")
            registerPassWord=bundle.getString("password","")

        }

父Fragment与子Fragment之间:
父Fragment:
发送数据:

        parentFragment.setFragmentResult("numberKey",result)

接受数据:

parentFragmentManager.setFragmentResultListener("numberKey",this,
            FragmentResultListener { requestKey, result ->
                registerAccount=result.getString("account","")
                registerPassWord=result.getString("password","")
            })

子Fragment,和上面差不多,只是把parentFragmentManager改为用childFragmentManager

好啦,目前踩坑到这里,后面项目中使用继续遇到坑的话,会继续补充的!!!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,616评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,020评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,078评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,040评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,154评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,265评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,298评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,072评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,491评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,795评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,970评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,654评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,272评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,985评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,815评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,852评论 2 351

推荐阅读更多精彩内容