Navagation導(dǎo)航使用以及踩坑解決方案

哎呀救拉,之前一段時間都在忙,難得周日放假就繼續(xù)寫下吧瘫拣,因為之前需要技術(shù)分享亿絮,所以就研究下了Jetpack的組件Navgation導(dǎo)航,這個組件的功能很強大麸拄,而且實用性感覺很強壹无,打算下一步去重構(gòu)項目的時候就使用他進行重構(gòu),他可以做到什么呢感帅?官方的文檔給出了答案:
官方地址:
https://developer.android.com/guide/navigation
導(dǎo)航組件提供各種其他優(yōu)勢斗锭,包括以下內(nèi)容:

  • 處理 Fragment 事務(wù)。
  • 默認情況下失球,正確處理往返操作岖是。
  • 為動畫和轉(zhuǎn)換提供標準化資源。
  • 實現(xiàn)和處理深層鏈接实苞。
  • 包括導(dǎo)航界面模式(例如抽屜式導(dǎo)航欄和底部導(dǎo)航)豺撑,用戶只需完成極少的額外工作。
  • Safe Args - 可在目標之間導(dǎo)航和傳遞數(shù)據(jù)時提供類型安全的 Gradle 插件黔牵。
  • ViewModel 支持 - 您可以將 ViewModel 的范圍限定為導(dǎo)航圖聪轿,以在圖表的目標之間共享與界面相關(guān)的數(shù)據(jù)
    然后說下怎么使用吧
    請在你項目中的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

    進入剛剛我們創(chuàng)建的resourceFile后,就可以在左上角看到創(chuàng)建按鈕猾浦,然后陆错,在看到下面的這些Fragment都是我提前創(chuàng)建好的
    截屏2020-12-13 上午9.20.55.png

看灯抛,這是我這邊提前寫好的布局,是不是從右圖就可以清晰的看到我這個頁面可以前往哪些頁面音瓷,并知道與哪些頁面產(chǎn)生了關(guān)聯(lián)对嚼?
然后我就繼續(xù)說下這些是如何做到的:
在我們點擊右上角的Design,你就可以看到下面這些屬性:


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

先從Arguments開始說起:


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

這個是啟動Fragment的時候攜帶的參數(shù)绳慎,Name參數(shù)的名字纵竖,Type參數(shù)的類型,下面是數(shù)組和可空杏愤,然后Default Value為默認參數(shù)

然后是Actions靡砌,這個就比較重要了,他是決定著這個頁面可以與哪些頁面產(chǎn)生關(guān)聯(lián)的


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

ID:是后面執(zhí)行跳轉(zhuǎn)的時候珊楼,需要用到的重要屬性通殃,這個會告訴navControlln需要執(zhí)行哪個頁面到哪個頁面的跳轉(zhuǎn)
From:告訴navControlln,是從哪個頁面出發(fā)
Destination:從From的這個頁面到達哪個頁面
然后下面都是些場景轉(zhuǎn)換和動畫效果實現(xiàn)的屬性

DeepLinks:是深度鏈接的
這個深度鏈接就比較有意思了亥曹,在外部引用拉起我們app的某個頁面的時候就可以用上了,但是他并不是直接達到這個頁面的恨诱,而是把需要通往這個頁面的路徑上的頁面全部都拉起媳瞪,才會產(chǎn)生直接達到這個頁面的效果,舉例吧:
我在某個網(wǎng)頁上產(chǎn)生了點擊事件照宝,而這個點擊事件會拉起我們應(yīng)用的D頁面蛇受,但是在打開D頁面之前需要打開ABC三個頁面,而通過深度鏈接去打開D頁面的時候厕鹃,前面ABC三個頁面也會被拉起

當然兢仰,我們不單單是在這里進行各種設(shè)置,頁面通過直接拉動的方式剂碴,在布局中調(diào)整的把将,因為我不會上傳git動畫,所以各位看官可以自己試試

在這個過程中忆矛,如何體現(xiàn)Navigation方便管理Fragment的呢察蹲?畢竟前面都是說布局的屬性怎么用而已,是不是催训?但是對我的理解來說洽议,單單從布局中就可以清晰看到了每個頁面是如何讓關(guān)聯(lián)的,就已經(jīng)很舒服了(畢竟有時候看項目的時候找頁面真的很蛋疼漫拭,尤其是新接觸項目的時候)

因為navigation很多人都寫了亚兄,所以我把大佬們寫得比較好而且清晰的文章也附上吧,我就不再進行過多的重復(fù)說明了:
《Android導(dǎo)航組件Navigation從入門到精通》
https://blog.csdn.net/yingaizhu/article/details/105972720

后面我繼續(xù)說我在項目中實際運用中遇到的問題采驻,還有解決方法:
1 配置問題:因為在配置navigation的時候發(fā)現(xiàn)會和當前的gradle插件版本發(fā)生沖突問題:
解決辦法:
方案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 出現(xiàn)無法找到導(dǎo)航圖的問題:
解決方案:
1 檢查在xml文件中菲盾,是否設(shè)置了navGraph的資源文件
2 如果設(shè)置了還是無法找到颓影,請點擊右上角的扳手的圖標


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

3 使用組件自帶fragmentNavigator跳轉(zhuǎn)時候,出現(xiàn)重復(fù)新建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,則創(chuàng)建
            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)
        // 創(chuàng)建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節(jié)點
        navController.setGraph(R.navigation.nav_setting, bundleOf(FRAGMENT_DATA_IS_SET to isSetPassword))

3 在導(dǎo)航圖中把<fragment>標簽修改為<fix_fragment>

4 把承載navigation導(dǎo)航圖的<fragment>中的navGraph屬性刪除城豁,否則會繼續(xù)使用原本的FragmentNavigator
這樣,就可以把我們的自定義的FragmentNavigator進行使用了

4 傳參數(shù)問題:
這個就很蛋疼了抄课,因為我是在重構(gòu)項目中開始使用的唱星,但是,在別的頁面開始進行navigation的使用的時候跟磨,需要把上一個頁面的參數(shù)傳進來间聊,所以我會優(yōu)先考慮使用FragmentManager的進行傳遞(因為這不是navigation的范圍了),但是有個很重要的因素抵拘,就是他們使用的FragmentManager不是同一個哎榴,所以會導(dǎo)致數(shù)據(jù)傳遞失敗了!=┲搿尚蝌!
解決方法:
1 把上一個頁面的參數(shù),傳到承載navigation容器的Activity/Fragment中充尉,然后然后通過以下方式飘言,把數(shù)據(jù)傳入到起始目的地

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

2 fragment之間傳遞參數(shù)
發(fā)送數(shù)據(jù):

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

接受數(shù)據(jù):

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

3 fragment向activity傳遞參數(shù)
發(fā)送數(shù)據(jù)同上

接受數(shù)據(jù):

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

4 如果Fragment中的參數(shù)需要用到其他頁面,但并是不導(dǎo)航圖的下一個目的地驼侠,請使用FragmentManager進行數(shù)據(jù)傳遞:
同級Fragment中:
發(fā)送數(shù)據(jù):

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

接受數(shù)據(jù):

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

        }

父Fragment與子Fragment之間:
父Fragment:
發(fā)送數(shù)據(jù):

        parentFragment.setFragmentResult("numberKey",result)

接受數(shù)據(jù):

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

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

好啦泪电,目前踩坑到這里般妙,后面項目中使用繼續(xù)遇到坑的話,會繼續(xù)補充的O嗨佟5臁!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末突诬,一起剝皮案震驚了整個濱河市苫拍,隨后出現(xiàn)的幾起案子芜繁,更是在濱河造成了極大的恐慌,老刑警劉巖绒极,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件骏令,死亡現(xiàn)場離奇詭異,居然都是意外死亡垄提,警方通過查閱死者的電腦和手機榔袋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來铡俐,“玉大人凰兑,你說我怎么就攤上這事∩笄穑” “怎么了吏够?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長滩报。 經(jīng)常有香客問我锅知,道長,這世上最難降的妖魔是什么脓钾? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任售睹,我火速辦了婚禮,結(jié)果婚禮上惭笑,老公的妹妹穿的比我還像新娘侣姆。我一直安慰自己生真,他們只是感情好沉噩,可當我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著柱蟀,像睡著了一般川蒙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上长已,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天畜眨,我揣著相機與錄音,去河邊找鬼术瓮。 笑死康聂,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的胞四。 我是一名探鬼主播恬汁,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼辜伟!你這毒婦竟也來了氓侧?” 一聲冷哼從身側(cè)響起蒲稳,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤睁壁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體啊犬,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年果漾,在試婚紗的時候發(fā)現(xiàn)自己被綠了壳猜。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡囚聚,死狀恐怖靖榕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情顽铸,我是刑警寧澤茁计,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站谓松,受9級特大地震影響星压,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鬼譬,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一娜膘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧优质,春花似錦竣贪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至避乏,卻和暖如春爷耀,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背拍皮。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工歹叮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人铆帽。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓咆耿,卻偏偏與公主長得像,于是被迫代替她去往敵國和親爹橱。 傳聞我的和親對象是個殘疾皇子萨螺,可洞房花燭夜當晚...
    茶點故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內(nèi)容