developer.android.google.cn/jetpack
對(duì)navigation中action參數(shù)的destination,popUpTo,popUpToInclusive的一點(diǎn)筆記
destination顧名思義是導(dǎo)航的目的地,popUpTo(tag) 跳轉(zhuǎn)到tag,并彈出tag之上的fragment,popUpToInclusive=true會(huì)彈出tag,false則不會(huì)彈出。
【Android進(jìn)階必學(xué)】JetPack指路明燈—Navigation
Fragment不用再每次切換是都重新創(chuàng)建了
jetpack系列之自定義FragmentNavigator
Android Navigation 如何動(dòng)態(tài)的更換StartDestination &&保存Fragment狀態(tài)
Google控件Navigation進(jìn)階使用
2.統(tǒng)一配置Animation
fun getObjectNacAction(id: Int, singleTop: Boolean = true, popid: Int? = null, clearPop: Boolean = false) =
NavActionBuilder().apply {
destinationId = id
navOptions {
if (popid != null) {
popUpTo(popid) {
inclusive = clearPop
}
}
launchSingleTop = singleTop
anim {
popEnter = R.anim.slide_left_in
popExit = R.anim.slide_left_out
enter = R.anim.push_left_in
exit = R.anim.push_right_out
}
}
}
開(kāi)始使用 Navigation
要避免這種重復(fù)的話(huà),可以在從C 到A 的action中指定 app:popUpTo 還有 app:popUpToInclusive 屬性,如下:
<fragment
android:id="@+id/c"
android:name="com.example.myapplication.C"
android:label="fragment_c"
tools:layout="@layout/fragment_c">
<action
android:id="@+id/action_c_to_a"
app:destination="@id/a"
app:popUpTo="@+id/a"
app:popUpToInclusive="true"/>
</fragment>
在到達(dá)了目的地 C 之后,回退棧中包含了目的地 A,B,C 的實(shí)
例.當(dāng)導(dǎo)航回目的地 A 的時(shí)候,我們同時(shí) popUpTo A ,這意味著
我們?cè)趯?dǎo)航的時(shí)候從回退棧中刪除了 B,C. 伴隨著
app:popUpToInclusive="true" 我們同時(shí)彈出了回退棧中的第
一個(gè) A.要注意如果你沒(méi)有使用 app:popUpToInclusive="true"
這個(gè)屬性的話(huà),你的回退棧中將會(huì)包含兩個(gè)A 的實(shí)例.
兩種跳轉(zhuǎn)分別是傳入nav_graph.xml當(dāng)中的action id和resource id。
兩種方法都可以實(shí)現(xiàn)跳轉(zhuǎn),但是我更建議用第一種,因?yàn)榈谝环N可以配合著過(guò)渡的動(dòng)畫(huà)使用。
可以看到當(dāng)我們倒C之后,后臺(tái)堆棧當(dāng)中包括A、B、C單個(gè)實(shí)例。當(dāng)我們通過(guò)popUpTo A回到A的時(shí)候,意味著我們從堆棧當(dāng)中把B和C刪除了。當(dāng)我們使用app:popUpToInclusive =“true”的時(shí)候,我們還會(huì)把A彈出堆棧并有效的清除它。如果我們沒(méi)有使用app:popUpToInclusive =“true”那么也意味著我們的堆棧當(dāng)中包含兩個(gè)A的實(shí)例。
<action
android:id="@+id/action_c_to_a"
app:destination="@id/a"
app:popUpTo="@+id/a"
app:popUpToInclusive="true"/>
Navigation.findNavController(it).navigate(R.id.twoFragment)
對(duì)于Button控件來(lái)說(shuō),還有另一種實(shí)現(xiàn)跳轉(zhuǎn)的方法
view?.let { Navigation.findNavController(it).navigate(R.id.twoFragment)
問(wèn)題即使以前的片段名為popUpTo的根,Android導(dǎo)航框架仍會(huì)顯示后退箭頭
使用Android導(dǎo)航框架時(shí),我將IntroFragment作為根,將MainFragment作為IntroFragment的目標(biāo) . 在IntroFragment中我打電話(huà):
view.findNavController().navigate(IntroFragmentDirections.actionIntroFragmentToMainFragment())
該操作的xml是:
<action android:id="@+id/action_introFragment_to_mainFragment" app:destination="@id/mainFragment"
app:exitAnim="@anim/slide_out_right" app:popUpTo="@+id/main" app:popUpToInclusive="true"/>
盡管有popUpToInclusive,但MainFragment工具欄中仍然會(huì)出現(xiàn)一個(gè)后退箭頭 . 我一直無(wú)法找到擺脫它的方法 . 我已經(jīng)確認(rèn)按后面的軟鍵確實(shí)會(huì)退出活動(dòng) .
Navigation問(wèn)題詳解——Fragment創(chuàng)建新的實(shí)例
···
這樣就會(huì)出現(xiàn)一個(gè)問(wèn)題,可能我們之前的OneFragment保存一些狀態(tài)或者數(shù)據(jù),當(dāng)我們跳轉(zhuǎn)OneFragment的時(shí)候,因?yàn)閯?chuàng)建新的實(shí)例導(dǎo)致我們之前保存狀態(tài)或者數(shù)據(jù)全部消失。
3.resource id替換為action id
看過(guò)我之前代碼的同學(xué),可能會(huì)注意到我特意強(qiáng)調(diào)
findNavController().navigate(R.id.action_oneFragment_to_twoFragment)
這里的id最好用action id,這一種可以配合著action里的動(dòng)畫(huà)使用,當(dāng)然也可以配合著action里的屬性popUpTo popUpToInclusive來(lái)使用。
之前有位同學(xué)在公共號(hào)給我留言,問(wèn)下了Demo之后,為什么設(shè)置 popUpTo/popUpToInclusive不起作用,問(wèn)題就在這里應(yīng)該是action Id
問(wèn)題依舊....
看似,我們?cè)诨赝说臅r(shí)候,正常了。但是,我們的問(wèn)題依舊存在,每次點(diǎn)擊的時(shí)候依然會(huì)創(chuàng)建一個(gè)新的Fragment,一個(gè)新的實(shí)例,只是我們?cè)趧?chuàng)建新的實(shí)例的時(shí)候,把之前的給清除了而已。
我會(huì)在下一篇文章介紹如何解決這個(gè)問(wèn)題!
···
Jetpack源碼解析---Navigation為什么切換Fragment會(huì)重繪?
看到這里就很清楚了吧,F(xiàn)ragment的切換是通過(guò)replace方式
來(lái)切換的,并且加入回退棧,也就是說(shuō)每次切換Fragment,都
會(huì)銷(xiāo)毀視圖和重新創(chuàng)建視圖。至于為什么用這種方式我是真
的想不到,也沒(méi)搞清楚初衷是什么?按照我們目前的開(kāi)發(fā)來(lái)
說(shuō),F(xiàn)ragment的切換通常都會(huì)使用hide()、show(),而
replcae()的方式很少用,替換會(huì)把容器中的所有內(nèi)容全都替
換掉,有一些app會(huì)使用這樣的做法,保持只有一個(gè)fragment
在顯示,減少了界面的層級(jí)關(guān)系。
不僅僅是這樣,上篇文章有小伙伴問(wèn)切換了Fragment之后,
點(diǎn)擊返回按鈕,發(fā)現(xiàn)之前的Fragment重走了onCreateView流
程,這就意味著之前的狀態(tài)沒(méi)了。對(duì)于這個(gè)問(wèn)題其實(shí)根據(jù)上
面的分析,也能大概想到是因?yàn)槭裁?,但是返回按鈕的操作
我之前還真沒(méi)有看過(guò)源碼,所以這次順便了解一下:
到這里就基本結(jié)束了,我只分析了一個(gè)大概,可以了解到點(diǎn)擊返回按鈕,同樣也會(huì)重新創(chuàng)建視圖,也就是onCreateView會(huì)重新走一遍。
.1 建議
這里我的建議是:如果你的每個(gè)Fragment真的每次都需要重新繪制的話(huà),你可以考慮使用Navigation組件來(lái)實(shí)現(xiàn),畢竟通過(guò)Navgation組件真的很方便幫助我們切換導(dǎo)航,而且雖然布局會(huì)重新繪制,但是Google的官方Demo–SunFlower還是使用了這種方式,所以這里面我覺(jué)得:官方推薦我們使用Jetpack組件中的ViewModel、LiveData…等,可以發(fā)現(xiàn)SunFlowerdemo中,即便是切換Fragmengt也不會(huì)有很明顯的卡頓現(xiàn)象,因?yàn)槊總€(gè)Fragment即便重新繪制,但是View所對(duì)應(yīng)的ViewModel還在,數(shù)據(jù)并不需要重新加載或者請(qǐng)求,當(dāng)然這僅僅是我自己的看法啊.
但是如果你沒(méi)有這種場(chǎng)景的話(huà),建議還是用普通的方式我們自己來(lái)控制切換吧,這樣無(wú)論是基于Drawerlayout還是BottomNaivgationView的話(huà),我們可以自己實(shí)現(xiàn)切換。這塊我也不是很確定哈,也希望聽(tīng)取大家的意見(jiàn)和建議。
因?yàn)槊總€(gè)Fragment即便重新繪制,但是View所對(duì)應(yīng)的ViewModel還在,數(shù)據(jù)并不需要重新加載或者請(qǐng)求,
Android-Jetpack筆記-Navigation之Fragment支持復(fù)用
Jetpack路由組件學(xué)習(xí):深入理解功能強(qiáng)大的Navigation架構(gòu)之接管系統(tǒng)的返回操作
Android Navigation的四大要點(diǎn)你都知道嗎?
deepLink
popUpTo屬性表示堆棧返回到某個(gè)界面,其后的棧數(shù)據(jù)清空
//popUpToInclusive屬性為true表示回到指定界面時(shí),界面棧中是否還包括當(dāng)前界面
//(如果棧中已經(jīng)包含了指定要跳轉(zhuǎn)的界面,那么只會(huì)保留一個(gè),不指定則棧中會(huì)出現(xiàn)兩個(gè)
//界面相同的Fragment數(shù)據(jù))
a->b->c ,從c到a時(shí),指定app:popUpTo="@id/listFragment"
,app:popUpToInclusive="true" 發(fā)現(xiàn)是新開(kāi)了一個(gè)a,之前的a
走了,onDestroyView 和 onDestroy 方法,
如何 做到 從c到a時(shí)復(fù)用 a,也就是不銷(xiāo)毀之前的那個(gè),也不開(kāi)啟新的,直接復(fù)用老的fragment. 答案是使用popBackStack 方法,見(jiàn)下下圖:情況2
情況1
a(ListFragment),b(DetailFragment),c(EditorFragment) 三個(gè)fragment分別在 onCreate,onCreateView,onViewCreated,onDestroyView,onDestroy方法中加入日志,其中 b 啟動(dòng)c的 action中加入了 app:popUpTo="@id/listFragment" ,app:popUpToInclusive="true"
使用修復(fù)過(guò)的NavHostFragment

使用系統(tǒng)自帶的NavHostFragment

【Android進(jìn)階必學(xué)】JetPack指路明燈—Navigation
navigateUp
navigateUp與物理返回鍵的功能類(lèi)似,即返回當(dāng)前頁(yè)面堆棧的棧頂頁(yè)面,代碼如下所示。
Navigation.findNavController(it).navigateUp()
當(dāng)我們從A路由到B,B路由到C后,通過(guò)上面的代碼,使用navigateUp返回,則路由返回路徑為C到B,B到A,如果在A繼續(xù)調(diào)用navigateUp,則不會(huì)響應(yīng),因?yàn)楫?dāng)前棧中只有唯一一個(gè)頁(yè)面,而且是startDestination,所以不會(huì)再響應(yīng)返回操作。
情況2
popBackStack
navigateUp只能響應(yīng)向上一級(jí)的路由控制,而不能跨級(jí)進(jìn)行路由返回,popBackStack則是對(duì)其的補(bǔ)充,可以指定路由返回的action,代碼如下所示。
Navigation.findNavController(it).popBackStack(R.id.loginFragment, true)
當(dāng)我們從A路由到B,B路由到C后,通過(guò)popBackStack返回,指定要返回到的Fragment的id,即可直接返回到指定位置,第二個(gè)參數(shù)inclusive,代表返回操作是否包含指定的Fragment id。
這里要注意的是,當(dāng)你指定返回到A,同時(shí)inclusive為true的
時(shí)候,A也是不會(huì)被移除的,因?yàn)锳是棧頂。
但如下圖所示,界面并沒(méi)有執(zhí)行 a 的onDestroy 方法和
oncreate 方法 ,說(shuō)明 a 并沒(méi)有銷(xiāo)毀
使用系統(tǒng)自帶的NavHostFragment
inclusive為true時(shí)

inclusive為false時(shí)

使用修改過(guò)的NavHostFragment
inclusive為true時(shí)

inclusive為false時(shí)

從驗(yàn)證結(jié)果看不管inclusive為false還是true, a 頁(yè)面始終沒(méi)有 被銷(xiāo)毀。
使用修改過(guò)的NavHostFragment和系統(tǒng)自帶的NavHostFragment我區(qū)別?
修改過(guò)的NavHostFragment 用action開(kāi)頁(yè)面時(shí)不會(huì)調(diào)用onDestroyView和onDestroy方法,而系統(tǒng)自帶的會(huì)調(diào)用。
再考慮下面這樣一個(gè)場(chǎng)景,A—B,B路由到C的時(shí)候,設(shè)置popUpTo="@id/A",如果popUpToInclusive=false,則跳轉(zhuǎn)到C之后的路由棧為A—C,如果設(shè)置為true,則只剩下A在路由棧中,代碼如下所示。
如上文字經(jīng)過(guò)驗(yàn)證發(fā)現(xiàn)是錯(cuò)的,正確的結(jié)論是:
使用修改過(guò)的NavHostFragment,popUpToInclusive=true的情況

使用修改過(guò)的NavHostFragment, popUpToInclusive=false的情況

場(chǎng)景,A—B,B路由到C的時(shí)候,設(shè)置popUpTo="@id/A",如果popUpToInclusive=false,則跳轉(zhuǎn)到C之后的路由棧為A—C,如果設(shè)置為true,則只剩下c在路由棧中,