Android Fragment+ViewPager 組合壶冒,一些你不可不知的注意事項

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

基本使用


對于這種組合使用,ViewPager 提供了兩種頁面適配器來管理不同 Fragment 之間的滑動切換: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();
        }
    }

如上述代碼所示烟馅,沒有特別要求的話说庭,無論是哪種適配器類,實現(xiàn)起來都比簡單郑趁,不需要像普通的 ViewPager + View 組合那樣刊驴,還需處理視圖的初始化工作(instantiateItem方法)和銷毀(destroyItem方法)等。FragmentPagerAdapterFragmentStatePagerAdapter 在內(nèi)部已經(jīng)默認(rèn)實現(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 時捆憎,ViewPager 中的所有 Fragment 實例常駐內(nèi)存,當(dāng) Fragment 變得不可見時僅僅是視圖結(jié)構(gòu)的銷毀梭纹,即調(diào)用了 onDestroyView 方法躲惰。由于 FragmentPagerAdapter 內(nèi)存消耗較大,所以適合少量靜態(tài)頁面的場景栗柒。

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

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

新建一個名為 BaseFragment 的基類名扛,繼承自 Fragment,重寫
setUserVisibleHint 和 各個生命周期函數(shù)茧痒,添加日志打印肮韧。然后新建四個子類,分別命名為 OneFragment旺订、TwoFragment弄企、ThreeFragment 和 FourFragment,按照基本使用方法寫好代碼区拳,運行并滑動頁面拘领,查看日志打印。

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

使用 FragmentPagerAdapter 時:

使用 FragmentStatePagerAdapter 時:

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

Fragment 懶加載


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

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

ViewPager 本質(zhì)上是通過 Fragment 調(diào)用 setUserVisibleHint 方法實現(xiàn) Fragment 頁面的展示與隱藏枚冗,這一點從FragmentPagerAdapterFragmentStatePagerAdapter 的源碼和上面的截圖中都可以看出。那么對應(yīng)的解決方案就有了蛇损,自定義一個 LazyLoadFragment 基類官紫,利用 setUserVisibleHint 和 生命周期方法,通過對 Fragment 狀態(tài)判斷州藕,進行數(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 中實現(xiàn) requestData 方法即可毁涉。這里添加了一個 isDataLoaded 變量,目的是避免重復(fù)加載數(shù)據(jù)锈死∑堆撸考慮到有時候需要刷新數(shù)據(jù)的問題,便提供了一個用于強制刷新的參數(shù)判斷待牵。這種思路來自于 這篇文章其屏,在此基礎(chǔ)上做了一些修改。實際上缨该,在項目開發(fā)過程中偎行,還需處理網(wǎng)絡(luò)請求失敗等特殊情況,我想贰拿,了解原理之后蛤袒,這些問題都不再是問題。

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


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

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

一種是通過 setOffscreenPageLimit 方法設(shè)置保留視圖結(jié)構(gòu)的 Fragment 數(shù)量缩功,比較簡單晴及,比如保留所有 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)就不會銷毀啦桌,換一種角度解決了 Fragment 狀態(tài)保存與恢復(fù)的問題溯壶。

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末若治,一起剝皮案震驚了整個濱河市慨蓝,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌端幼,老刑警劉巖礼烈,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異婆跑,居然都是意外死亡此熬,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門滑进,熙熙樓的掌柜王于貴愁眉苦臉地迎上來摹迷,“玉大人,你說我怎么就攤上這事郊供∠康铮” “怎么了?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵驮审,是天一觀的道長鲫寄。 經(jīng)常有香客問我,道長疯淫,這世上最難降的妖魔是什么地来? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮熙掺,結(jié)果婚禮上未斑,老公的妹妹穿的比我還像新娘。我一直安慰自己币绩,他們只是感情好蜡秽,可當(dāng)我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布府阀。 她就那樣靜靜地躺著,像睡著了一般芽突。 火紅的嫁衣襯著肌膚如雪试浙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天寞蚌,我揣著相機與錄音田巴,去河邊找鬼。 笑死挟秤,一個胖子當(dāng)著我的面吹牛壹哺,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播艘刚,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼斗躏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了昔脯?” 一聲冷哼從身側(cè)響起啄糙,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎云稚,沒想到半個月后隧饼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡静陈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年燕雁,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鲸拥。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡拐格,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出刑赶,到底是詐尸還是另有隱情捏浊,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布撞叨,位于F島的核電站金踪,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏牵敷。R本人自食惡果不足惜胡岔,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望枷餐。 院中可真熱鬧靶瘸,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至惊暴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間趁桃,已是汗流浹背辽话。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留卫病,地道東北人油啤。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像蟀苛,于是被迫代替她去往敵國和親益咬。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,086評論 2 355

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

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