哎呀,之前一段時(shí)間都在忙,難得周日放假就繼續(xù)寫下吧,因?yàn)橹靶枰夹g(shù)分享,所以就研究下了Jetpack的組件Navgation導(dǎo)航,這個(gè)組件的功能很強(qiáng)大,而且實(shí)用性感覺很強(qiáng),打算下一步去重構(gòu)項(xiàng)目的時(shí)候就使用他進(jìn)行重構(gòu),他可以做到什么呢?官方的文檔給出了答案:
官方地址:
https://developer.android.com/guide/navigation
導(dǎo)航組件提供各種其他優(yōu)勢(shì),包括以下內(nèi)容:
- 處理 Fragment 事務(wù)。
- 默認(rèn)情況下,正確處理往返操作。
- 為動(dòng)畫和轉(zhuǎn)換提供標(biāo)準(zhǔn)化資源。
- 實(shí)現(xiàn)和處理深層鏈接。
- 包括導(dǎo)航界面模式(例如抽屜式導(dǎo)航欄和底部導(dǎo)航),用戶只需完成極少的額外工作。
- Safe Args - 可在目標(biāo)之間導(dǎo)航和傳遞數(shù)據(jù)時(shí)提供類型安全的 Gradle 插件。
-
ViewModel支持 - 您可以將ViewModel的范圍限定為導(dǎo)航圖,以在圖表的目標(biāo)之間共享與界面相關(guān)的數(shù)據(jù)
然后說下怎么使用吧
請(qǐng)?jiān)谀沩?xiàng)目中的project/app/src/main/src的目錄下新建Android Resource File文件:如圖截屏2020-12-13 上午9.15.49.png
然后你就會(huì)在res/navation的文件夾中看到你新建的文件
截屏2020-12-13 上午9.16.45.png
截屏2020-12-13 上午9.18.56.png
進(jìn)入剛剛我們創(chuàng)建的resourceFile后,就可以在左上角看到創(chuàng)建按鈕,然后,在看到下面的這些Fragment都是我提前創(chuàng)建好的
截屏2020-12-13 上午9.20.55.png
看,這是我這邊提前寫好的布局,是不是從右圖就可以清晰的看到我這個(gè)頁面可以前往哪些頁面,并知道與哪些頁面產(chǎn)生了關(guān)聯(lián)?
然后我就繼續(xù)說下這些是如何做到的:
在我們點(diǎn)擊右上角的Design,你就可以看到下面這些屬性:

先從Arguments開始說起:

這個(gè)是啟動(dòng)Fragment的時(shí)候攜帶的參數(shù),Name參數(shù)的名字,Type參數(shù)的類型,下面是數(shù)組和可空,然后Default Value為默認(rèn)參數(shù)
然后是Actions,這個(gè)就比較重要了,他是決定著這個(gè)頁面可以與哪些頁面產(chǎn)生關(guān)聯(lián)的

ID:是后面執(zhí)行跳轉(zhuǎn)的時(shí)候,需要用到的重要屬性,這個(gè)會(huì)告訴navControlln需要執(zhí)行哪個(gè)頁面到哪個(gè)頁面的跳轉(zhuǎn)
From:告訴navControlln,是從哪個(gè)頁面出發(fā)
Destination:從From的這個(gè)頁面到達(dá)哪個(gè)頁面
然后下面都是些場景轉(zhuǎn)換和動(dòng)畫效果實(shí)現(xiàn)的屬性
DeepLinks:是深度鏈接的
這個(gè)深度鏈接就比較有意思了,在外部引用拉起我們app的某個(gè)頁面的時(shí)候就可以用上了,但是他并不是直接達(dá)到這個(gè)頁面的,而是把需要通往這個(gè)頁面的路徑上的頁面全部都拉起,才會(huì)產(chǎn)生直接達(dá)到這個(gè)頁面的效果,舉例吧:
我在某個(gè)網(wǎng)頁上產(chǎn)生了點(diǎn)擊事件,而這個(gè)點(diǎn)擊事件會(huì)拉起我們應(yīng)用的D頁面,但是在打開D頁面之前需要打開ABC三個(gè)頁面,而通過深度鏈接去打開D頁面的時(shí)候,前面ABC三個(gè)頁面也會(huì)被拉起
當(dāng)然,我們不單單是在這里進(jìn)行各種設(shè)置,頁面通過直接拉動(dòng)的方式,在布局中調(diào)整的,因?yàn)槲也粫?huì)上傳git動(dòng)畫,所以各位看官可以自己試試
在這個(gè)過程中,如何體現(xiàn)Navigation方便管理Fragment的呢?畢竟前面都是說布局的屬性怎么用而已,是不是?但是對(duì)我的理解來說,單單從布局中就可以清晰看到了每個(gè)頁面是如何讓關(guān)聯(lián)的,就已經(jīng)很舒服了(畢竟有時(shí)候看項(xiàng)目的時(shí)候找頁面真的很蛋疼,尤其是新接觸項(xiàng)目的時(shí)候)
因?yàn)閚avigation很多人都寫了,所以我把大佬們寫得比較好而且清晰的文章也附上吧,我就不再進(jìn)行過多的重復(fù)說明了:
《Android導(dǎo)航組件Navigation從入門到精通》
https://blog.csdn.net/yingaizhu/article/details/105972720
后面我繼續(xù)說我在項(xiàng)目中實(shí)際運(yùn)用中遇到的問題,還有解決方法:
1 配置問題:因?yàn)樵谂渲胣avigation的時(shí)候發(fā)現(xiàn)會(huì)和當(dāng)前的gradle插件版本發(fā)生沖突問題:
解決辦法:
方案1:把gradle版本升級(jí)到最高版本,這樣就可以直接使用,但是謹(jǐn)慎升級(jí)gradle插件版本,因?yàn)轫?xiàng)目中可能會(huì)有其他組件使用沖突問題
方案2:在項(xiàng)目中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è)置了還是無法找到,請(qǐng)點(diǎn)擊右上角的扳手的圖標(biāo)

3 使用組件自帶fragmentNavigator跳轉(zhuǎn)時(shí)候,出現(xiàn)重復(fù)新建fragment對(duì)象問題:
解決方案:請(qǐng)自定義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
}
//當(dāng)前正在顯示的fragment
val fragment=mManager.primaryNavigationFragment
if (fragment!=null){
//隱藏正在顯示的頁面,準(zhǔn)備顯示新的潔面
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 請(qǐng)?jiān)谑褂胣avigation組件的容器的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對(duì)象
val navigator = FixFragmentNavigator(
this,
navHostFragment.childFragmentManager,
R.id.fragment_main
)
// 添加 key, value
navController.navigatorProvider.addNavigator("fix_fragment", navigator)
// 要在 CustomNavigator 類被加載之后添加graph,不然找不到 fix_fragment節(jié)點(diǎn)
navController.setGraph(R.navigation.nav_setting, bundleOf(FRAGMENT_DATA_IS_SET to isSetPassword))
3 在導(dǎo)航圖中把<fragment>標(biāo)簽修改為<fix_fragment>
4 把承載navigation導(dǎo)航圖的<fragment>中的navGraph屬性刪除,否則會(huì)繼續(xù)使用原本的FragmentNavigator
這樣,就可以把我們的自定義的FragmentNavigator進(jìn)行使用了
4 傳參數(shù)問題:
這個(gè)就很蛋疼了,因?yàn)槲沂窃谥貥?gòu)項(xiàng)目中開始使用的,但是,在別的頁面開始進(jìn)行navigation的使用的時(shí)候,需要把上一個(gè)頁面的參數(shù)傳進(jìn)來,所以我會(huì)優(yōu)先考慮使用FragmentManager的進(jìn)行傳遞(因?yàn)檫@不是navigation的范圍了),但是有個(gè)很重要的因素,就是他們使用的FragmentManager不是同一個(gè),所以會(huì)導(dǎo)致數(shù)據(jù)傳遞失敗了?。?!
解決方法:
1 把上一個(gè)頁面的參數(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)航圖的下一個(gè)目的地,請(qǐng)使用FragmentManager進(jìn)行數(shù)據(jù)傳遞:
同級(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
好啦,目前踩坑到這里,后面項(xiàng)目中使用繼續(xù)遇到坑的話,會(huì)繼續(xù)補(bǔ)充的!?。?/p>



