上篇文章Android-Jetpack筆記-Navigation之Fragment使用
提到,每次切換目的地,fragment是反復(fù)銷毀重建的,按照谷歌推薦的1個(gè)APP只需1個(gè)activity
的思路開發(fā),這樣是沒問題的,但是這里的fragment是作為首頁的3個(gè)常駐頁面,我們是希望能夠保存起來的,畢竟,銷毀重建需要重新請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù),重新初始化view,嚴(yán)重影響用戶體驗(yàn),那么接下來繼續(xù)上篇文章的分析,支持Fragment的復(fù)用。
本文源碼基于SDK 29
,IDE是Android studio 3.5.3
解決
給上篇文章的項(xiàng)目加些日志,從面板頁切到通知頁,再從通知頁切回面板頁,查看日志,
可見面板頁發(fā)生了銷毀重建,接著創(chuàng)建一個(gè)FixFragmentNavigator
繼承FragmentNavigator
并重寫navigate
方法,直接把父類的實(shí)現(xiàn)copy過來,第1步,在調(diào)用fragment初始化的地方,改成復(fù)用:
//FixFragmentNavigator.java
NavDestination navigate(){
//fix 1: 把類名作為tag,尋找已存在的Fragment
//(如果想只針對(duì)個(gè)別fragment進(jìn)行保活復(fù)用,可以在tag上做些標(biāo)記比如加個(gè)前綴,這里不再展開)
Fragment frag = mFragmentManager.findFragmentByTag(className);
if (null == frag) {
//不存在,則創(chuàng)建
frag = instantiateFragment(mContext, mFragmentManager, className, args);
}
}
第2步,將ft.replace
換成show和hide
,
//FixFragmentNavigator.java
NavDestination navigate(){
// ft.replace(mContainerId, frag);
//fix 2: replace換成show和hide
List<Fragment> fragments = mFragmentManager.getFragments();
for (Fragment fragment : fragments) {
ft.hide(fragment);
}
if (!frag.isAdded()) {
ft.add(mContainerId, frag, className);
}
ft.show(frag);
ft.setPrimaryNavigationFragment(frag);
}
第3步,反射獲取父類的mBackStack
,
//FixFragmentNavigator.java
NavDestination navigate(){
//fix 3: mBackStack是私有的,而且沒有暴露出來,只能反射獲取
ArrayDeque<Integer> mBackStack;
try {
Field field = FragmentNavigator.class.getDeclaredField("mBackStack");
field.setAccessible(true);
mBackStack = (ArrayDeque<Integer>) field.get(this);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
第4步,把父類的私有方法generateBackStackName
也copy過來,
//FixFragmentNavigator.java
//fix 4: 從父類那邊copy過來即可
private String generateBackStackName(int backStackIndex, int destId) {
return backStackIndex + "-" + destId;
}
第5步,給FixFragmentNavigator
加上注解給他取個(gè)名字,原因放后面說,
@Navigator.Name("fixFragment")
//fix 5: 需要指定1個(gè)名字,源碼里自帶的名字有navigation、activity、fragment、dialog
class FixFragmentNavigator extends FragmentNavigator {
}
至此FixFragmentNavigator
就寫好了,完整代碼可以查看Jetpack筆記代碼,接下來要如何把他使用進(jìn)去呢?
先在布局文件中去掉app:navGraph="@navigation/mobile_navigation"
,然后來到activity,編寫如下代碼,
//NavigationActivity.java
void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_navigation);
BottomNavigationView navView = findViewById(R.id.nav_view);
//獲取頁面容器NavHostFragment
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
//獲取導(dǎo)航控制器
NavController navController = NavHostFragment.findNavController(fragment);
//創(chuàng)建自定義的Fragment導(dǎo)航器
FixFragmentNavigator fragmentNavigator =
new FixFragmentNavigator(this, fragment.getChildFragmentManager(), fragment.getId());
//獲取導(dǎo)航器提供者
NavigatorProvider provider = navController.getNavigatorProvider();
//把自定義的Fragment導(dǎo)航器添加進(jìn)去
provider.addNavigator(fragmentNavigator);
//手動(dòng)創(chuàng)建導(dǎo)航圖
NavGraph navGraph = initNavGraph(provider, fragmentNavigator);
//設(shè)置導(dǎo)航圖
navController.setGraph(navGraph);
//底部導(dǎo)航設(shè)置點(diǎn)擊事件
navView.setOnNavigationItemSelectedListener(item -> {
navController.navigate(item.getItemId());
return true;
});
}
//手動(dòng)創(chuàng)建導(dǎo)航圖,把3個(gè)目的地添加進(jìn)來
private NavGraph initNavGraph(NavigatorProvider provider, FixFragmentNavigator fragmentNavigator) {
NavGraph navGraph = new NavGraph(new NavGraphNavigator(provider));
//用自定義的導(dǎo)航器來創(chuàng)建目的地
FragmentNavigator.Destination destination1 = fragmentNavigator.createDestination();
destination1.setId(R.id.navigation_home);
destination1.setClassName(HomeFragment.class.getCanonicalName());
destination1.setLabel(getResources().getString(R.string.title_home));
navGraph.addDestination(destination1);
//省略
navGraph.addDestination(destination2);
//省略
navGraph.addDestination(destination3);
navGraph.setStartDestination(R.id.navigation_home);
return navGraph;
}
具體流程都寫在代碼注釋里了,至此就實(shí)現(xiàn)了對(duì)fragment的復(fù)用。
前邊提到的自定義導(dǎo)航器需要指定名字@Navigator.Name("fixFragment")
,是因?yàn)椴煌愋偷哪康牡兀撁妫┬枰褂貌煌膶?dǎo)航器,在NavigatorProvider
里有個(gè)map存儲(chǔ)了多個(gè)導(dǎo)航器,
//NavigatorProvider.java
private final HashMap<String, Navigator<? extends NavDestination>> mNavigators =
new HashMap<>();
// "navigation" NavGraphNavigator
// "activity" ActivityNavigator
// "fragment" FragmentNavigator
// "dialog" DialogFragmentNavigator
// "fixFragment" FixFragmentNavigator 這個(gè)就是我們自定義的導(dǎo)航器
然后,使用自定義導(dǎo)航器FixFragmentNavigator
來createDestination
創(chuàng)建目的地,這樣就把導(dǎo)航器和目的地綁定在一起了。可以看出,Navigation
的思想是,把各種類型的頁面都抽象成目的地Destination
,進(jìn)行統(tǒng)一跳轉(zhuǎn),不同的導(dǎo)航器則封裝了不同類型頁面跳轉(zhuǎn)的實(shí)現(xiàn),由NavController
統(tǒng)一調(diào)度,而許許多多的目的地則編織成了一個(gè)導(dǎo)航圖NavGraph
。