Android Fragment+ViewPager 組合,一些你不可不知的注意事項(xiàng)

前面兩篇文章中,對(duì) Fragment 的基本使用、常見問題和狀態(tài)恢復(fù)做了詳細(xì)的分析總結(jié)。除了在 Activity 中單獨(dú)使用 Fragment,F(xiàn)ragment + ViewPager 組合也是項(xiàng)目中使用非常頻繁的方式,本文再來總結(jié)一下這種組合使用時(shí)的注意事項(xiàng)。在此之前,如果你對(duì) Fragment 的認(rèn)知和使用還有不清楚的地方,一定要先閱讀前面兩篇文章:

基本使用


對(duì)于這種組合使用,ViewPager 提供了兩種頁(yè)面適配器來管理不同 Fragment 之間的滑動(dòng)切換:FragmentPagerAdapterFragmentStatePagerAdapter。先來看一下他們的基本使用,稍后再分析二者之間的區(qū)別。

//    private class ContentPagerAdapter extends FragmentStatePagerAdapter{
    private class ContentPagerAdapter extends FragmentPagerAdapter{

        public ContentPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            return fragmentList.get(position);
        }

        @Override
        public int getCount() {
            return fragmentList.size();
        }
    }

如上述代碼所示,沒有特別要求的話,無論是哪種適配器類,實(shí)現(xiàn)起來都比簡(jiǎn)單,不需要像普通的 ViewPager + View 組合那樣,還需處理視圖的初始化工作(instantiateItem方法)和銷毀(destroyItem方法)等。FragmentPagerAdapterFragmentStatePagerAdapter 在內(nèi)部已經(jīng)默認(rèn)實(shí)現(xiàn)了這些功能。

兩種 PagerAdapter 區(qū)別


源碼定義中已經(jīng)很清楚地描述了 FragmentPagerAdapterFragmentStatePagerAdapter 的區(qū)別:

FragmentPagerAdapter

Implementation of PagerAdapter that represents each page as a Fragment that is persistently kept in the fragment manager as long as the user can return to the page.

This version of the pager is best for use when there are a handful of typically more static fragments to be paged through, such as a set of tabs. The fragment of each page the user visits will be kept in memory, though its view hierarchy may be destroyed when not visible. This can result in using a significant amount of memory since fragment instances can hold on to an arbitrary amount of state. For larger sets of pages, consider FragmentStatePagerAdapter.

FragmentStatePagerAdapter

Implementation of PagerAdapter that uses a Fragment to manage each page. This class also handles saving and restoring of fragment's state.

This version of the pager is more useful when there are a large number of pages, working more like a list view. When pages are not visible to the user, their entire fragment may be destroyed, only keeping the saved state of that fragment. This allows the pager to hold on to much less memory associated with each visited page as compared to FragmentPagerAdapter at the cost of potentially more overhead when switching between pages.

總結(jié)歸納如下:

使用 FragmentPagerAdapter 時(shí),ViewPager 中的所有 Fragment 實(shí)例常駐內(nèi)存,當(dāng) Fragment 變得不可見時(shí)僅僅是視圖結(jié)構(gòu)的銷毀,即調(diào)用了 onDestroyView 方法。由于 FragmentPagerAdapter 內(nèi)存消耗較大,所以適合少量靜態(tài)頁(yè)面的場(chǎng)景。

使用 FragmentStatePagerAdapter 時(shí),當(dāng) Fragment 變得不可見,不僅視圖層次銷毀,實(shí)例也被銷毀,即調(diào)用了 onDestroyViewonDestroy 方法,僅僅保存 Fragment 狀態(tài)。相比而言, FragmentStatePagerAdapter 內(nèi)存占用較小,所以適合大量動(dòng)態(tài)頁(yè)面,比如我們常見的新聞列表類應(yīng)用。

“Talk is cheap, show me the code.” 如果這樣表達(dá)還是不能理解二者之間的區(qū)別的話,最好的辦法就是用代碼來表達(dá)。

新建一個(gè)名為 BaseFragment 的基類,繼承自 Fragment,重寫
setUserVisibleHint 和 各個(gè)生命周期函數(shù),添加日志打印。然后新建四個(gè)子類,分別命名為 OneFragment、TwoFragment、ThreeFragment 和 FourFragment,按照基本使用方法寫好代碼,運(yùn)行并滑動(dòng)頁(yè)面,查看日志打印。

由于代碼較為簡(jiǎn)單,考慮內(nèi)容長(zhǎng)度,這里就不貼相關(guān)代碼,主要描述思想。對(duì)應(yīng)日志截圖如下:

使用 FragmentPagerAdapter 時(shí):

使用 FragmentStatePagerAdapter 時(shí):

圖中做了相應(yīng)標(biāo)記說明,二者區(qū)別一目了然,無需過多解釋。出現(xiàn)這樣的區(qū)別,其實(shí)從源碼中的 instantiateItemdestroyItem 也能讀出一二,感興趣的話可以翻看一下。

Fragment 懶加載


懶加載,顧名思義,是希望在展示相應(yīng) Fragment 頁(yè)面時(shí)再動(dòng)態(tài)加載頁(yè)面數(shù)據(jù),數(shù)據(jù)通常來自于網(wǎng)絡(luò)或本地?cái)?shù)據(jù)庫(kù)。這種做法的合理性在于用戶可能不會(huì)滑到一下頁(yè)面,同時(shí)還能幫助減輕當(dāng)前頁(yè)面數(shù)據(jù)請(qǐng)求的帶寬壓力,如果是用戶使用流量的話,還能避免無用的流量消耗。

從上面的截圖中可以看出,ViewPager 在展示當(dāng)前頁(yè)面時(shí),會(huì)同時(shí)預(yù)加載下一頁(yè)面。事實(shí)上,可以通過 ViewPager 提供的 setOffscreenPageLimit(int limit) 方法設(shè)置 ViewPager 預(yù)加載的頁(yè)面數(shù)量,默認(rèn)值為 1,并且這個(gè)參數(shù)的值不能小于 1。所以也就無法通過這個(gè)方法實(shí)現(xiàn) ViewPager 中 Fragment 的懶加載,一定要改 ViewPager 的話只能通過自定義一個(gè) Viewpager 類來實(shí)現(xiàn),這種做法就比較繁瑣。其實(shí)可以從 Fragment 下手。

ViewPager 本質(zhì)上是通過 Fragment 調(diào)用 setUserVisibleHint 方法實(shí)現(xiàn) Fragment 頁(yè)面的展示與隱藏,這一點(diǎn)從FragmentPagerAdapterFragmentStatePagerAdapter 的源碼和上面的截圖中都可以看出。那么對(duì)應(yīng)的解決方案就有了,自定義一個(gè) LazyLoadFragment 基類,利用 setUserVisibleHint 和 生命周期方法,通過對(duì) Fragment 狀態(tài)判斷,進(jìn)行數(shù)據(jù)加載,并將數(shù)據(jù)加載的接口提供開放出去,供子類使用。參考代碼如下:

public abstract class LazyLoadFragment extends BaseFragment {

    protected boolean isViewInitiated;
    protected boolean isDataLoaded;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        isViewInitiated = true;
        prepareRequestData();
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        prepareRequestData();
    }

    public abstract void requestData();

    public boolean prepareRequestData() {
        return prepareRequestData(false);
    }

    public boolean prepareRequestData(boolean forceUpdate) {
        if (getUserVisibleHint() && isViewInitiated && (!isDataLoaded || forceUpdate)) {
            requestData();
            isDataLoaded = true;
            return true;
        }
        return false;
    }

}

然后在子類 Fragment 中實(shí)現(xiàn) requestData 方法即可。這里添加了一個(gè) isDataLoaded 變量,目的是避免重復(fù)加載數(shù)據(jù)。考慮到有時(shí)候需要刷新數(shù)據(jù)的問題,便提供了一個(gè)用于強(qiáng)制刷新的參數(shù)判斷。這種思路來自于 這篇文章,在此基礎(chǔ)上做了一些修改。實(shí)際上,在項(xiàng)目開發(fā)過程中,還需處理網(wǎng)絡(luò)請(qǐng)求失敗等特殊情況,我想,了解原理之后,這些問題都不再是問題。

Fragment 狀態(tài)恢復(fù)問題


前文描述FragmentPagerAdapterFragmentStatePagerAdapter 的區(qū)別時(shí)有提到,這兩種適配器類默認(rèn)都會(huì)保存 Fragment 狀態(tài),包括 View 狀態(tài)和成員變量數(shù)據(jù)狀態(tài)。需要注意的是,View 狀態(tài)包括的內(nèi)容很多,比如用戶在 EditText 中輸入的內(nèi)容、ScrollView 滑動(dòng)的位置紀(jì)錄等。

有關(guān) Fragment 的具體使用細(xì)節(jié)和注意事項(xiàng)可以參考這篇文章:Android Activity 和 Fragment 狀態(tài)保存與恢復(fù)的最佳實(shí)踐。這里我說說另外兩種簡(jiǎn)單粗暴的做法。

一種是通過 setOffscreenPageLimit 方法設(shè)置保留視圖結(jié)構(gòu)的 Fragment 數(shù)量,比較簡(jiǎn)單,比如保留所有 Fragment 視圖結(jié)構(gòu): mContentVp.getAdapter().getCount()-1;另一種就是重寫適配器的 Fragment 相關(guān)方法,比如:

//    private class ContentPagerAdapter extends FragmentStatePagerAdapter {
    private class ContentPagerAdapter extends FragmentPagerAdapter {
    
        private FragmentManager fragmentManager;

        public ContentPagerAdapter(FragmentManager fm) {
            super(fm);
            this.fragmentManager = fm;
        }

        @Override
        public Fragment getItem(int position) {
            return fragmentList.get(position);
        }

        @Override
        public int getCount() {
            return fragmentList.size();
        }

        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            Fragment fragment = (Fragment) super.instantiateItem(container, position);
            this.fragmentManager.beginTransaction().show(fragment).commit();
            return fragment;
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) { 
            Fragment fragment = fragmentList.get(position);
            fragmentManager.beginTransaction().hide(fragment).commit();
        }

    }

這種處理下,也就不用區(qū)分使用的是哪種適配器類,通過重寫 instantiateItemdestroyItem 方法,使用 show 和 hide 方法處理 Fragment 的展示與隱藏,這樣,視圖結(jié)構(gòu)就不會(huì)銷毀,換一種角度解決了 Fragment 狀態(tài)保存與恢復(fù)的問題。

可以看出,使用這兩種處理方式時(shí),F(xiàn)ragment 實(shí)例均保存在內(nèi)存中,具有一定內(nèi)存消耗,適合于頁(yè)面較少的情況。至于大量頁(yè)面,還是推薦通過 Fragment 自帶的狀態(tài)保存與恢復(fù)方式處理。

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

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

  • Fragment,俗稱碎片,自 Android 3.0 開始被引進(jìn)并大量使用。然而就是這樣耳熟能詳?shù)囊粋€(gè)東西,在開...
    亦楓閱讀 24,068評(píng)論 9 84
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,349評(píng)論 25 708
  • 作者:李旺成 時(shí)間:2016年5月3日 一、PagerAdapter介紹 先看效果圖 PagerAdapter簡(jiǎn)介...
    diygreen閱讀 83,022評(píng)論 38 309
  • 從童年到現(xiàn)在 鄉(xiāng)言里的耍水從口中吐出來沒了棱角 是普通話中字正腔圓的游泳 從一絲不掛 到穿上泳褲泳帽泳鏡來遮羞 又...
    子夜玄白閱讀 402評(píng)論 2 1
  • 改自《得到》 現(xiàn)在惦記著長(zhǎng)輩手里積蓄的人很多。有的理財(cái)騙局,連我們自己都很難識(shí)破。 請(qǐng)把這張清單轉(zhuǎn)發(fā)給父母和你關(guān)心...
    劉清明70后閱讀 301評(píng)論 0 2