Android-Jetpack筆記-Navigation之Fragment支持復(fù)用

上篇文章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ù)用。

Jetpack筆記代碼

本文源碼基于SDK 29,IDE是Android studio 3.5.3

解決

給上篇文章的項(xiàng)目加些日志,從面板頁切到通知頁,再從通知頁切回面板頁,查看日志,

image

可見面板頁發(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)航器FixFragmentNavigatorcreateDestination創(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

參考

image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。