ViewPager FragmentPagerAdapter和FragmentStatePagerAdapter使用

在使用ViewPager常用設(shè)置
1)mViewPager.setOffscreenPageLimit(2);//設(shè)置緩存view 的個(gè)數(shù)(實(shí)際有3個(gè)伴榔,緩存2個(gè)+正在顯示的1個(gè))
2)mViewPager.setPageMargin((int)getResources().getDimensionPixelOffset(R.dimen.ui_5_dip));//設(shè)置viewpager每個(gè)頁卡的間距旭等,與gallery的spacing屬性類似
3)ViewPager更新數(shù)據(jù)問題:
直接使用notifyDataSetChanged是無法更新诱桂,需要同時(shí)重寫getItemPosition返回常量 POSITION_NONE (此常量為viewpager帶的)。

轉(zhuǎn): How to update and replace fragment in viewpager?

How to update and replace fragment in viewpager?

ListView的工作原理

在了解ViewPager的工作原理之前豪诲,先回顧ListView的工作原理:

  1. ListView只有在需要顯示某些列表項(xiàng)時(shí)榆芦,它才會(huì)去申請(qǐng)可用的視圖對(duì)象捕仔;如果為所有的列表項(xiàng)數(shù)據(jù)創(chuàng)建視圖對(duì)象,會(huì)浪費(fèi)內(nèi)存橡卤;
  2. ListView找誰去申請(qǐng)視圖對(duì)象呢扮念? 答案是adapter。adapter是一個(gè)控制器對(duì)象碧库,負(fù)責(zé)從模型層獲取數(shù)據(jù)柜与,創(chuàng)建并填充必要的視圖對(duì)象,將準(zhǔn)備好的視圖對(duì)象返回給ListView嵌灰;
  3. 首先弄匕,通過調(diào)用adapter的getCount()方法,ListView詢問數(shù)組列表中包含多少個(gè)對(duì)象(為避免出現(xiàn)數(shù)組越界的錯(cuò)誤)沽瞭;緊接著ListView就調(diào)用adapter的getView(int, View, ViewGroup)方法迁匠。

ViewPager某種程度上類似于ListView,區(qū)別在于:ListView通過ArrayAdapter.getView(int position, View convertView, ViewGroup parent)填充視圖;ViewPager通過FragmentPagerAdapter.getItem(int position)生成指定位置的fragment.

而我們需要關(guān)注的是:

ViewPager和它的adapter是如何配合工作的柒瓣?

聲明:本文內(nèi)容針對(duì)android.support.v4.app.*
ViewPager有兩個(gè)adapter:FragmentPagerAdapter和FragmentStatePagerAdapter:

繼承自android.support.v4.view.PagerAdapter儒搭,每頁都是一個(gè)Fragment,并且所有的Fragment實(shí)例一直保存在Fragment manager中芙贫。所以它適用于少量固定的fragment搂鲫,比如一組用于分頁顯示的標(biāo)簽。除了當(dāng)Fragment不可見時(shí)磺平,它的視圖層(view hierarchy)有可能被銷毀外魂仍,每頁的Fragment都會(huì)被保存在內(nèi)存中。(翻譯自代碼文件的注釋部分)

繼承自android.support.v4.view.PagerAdapter拣挪,每頁都是一個(gè)Fragment擦酌,當(dāng)Fragment不被需要時(shí)(比如不可見),整個(gè)Fragment都會(huì)被銷毀菠劝,除了saved state被保存外(保存下來的bundle用于恢復(fù)Fragment實(shí)例)赊舶。所以它適用于很多頁的情況。(翻譯自代碼文件的注釋部分)

它倆的子類赶诊,需要實(shí)現(xiàn)getItem(int)android.support.v4.view.PagerAdapter.getCount().

先通過一段代碼了解ViewPager和FragmentPagerAdapter的典型用法

稍后做詳細(xì)分析:

  // Set a PagerAdapter to supply views for this pager.
  ViewPager viewPager = (ViewPager) findViewById(R.id.my_viewpager_id);
  viewPager.setAdapter(mMyFragmentPagerAdapter);
 
  private FragmentPagerAdapter mMyFragmentPagerAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) {
    @Override
    public int getCount() {
      return 2; // Return the number of views available.
    }
 
    @Override
    public Fragment getItem(int position) {
      return new MyFragment(); // Return the Fragment associated with a specified position.
    }
 
    // Called when the host view is attempting to determine if an item's position has changed.
    @Override
    public int getItemPosition(Object object) {
      if (object instanceof MyFragment) {
        ((MyFragment)object).updateView();
      }
      return super.getItemPosition(object);
    }
  };
 
  private class MyFragment extends Fragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      // do something such as init data
    }
 
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
      View view = inflater.inflate(R.layout.fragment_my, container, false);
      // init view in the fragment
      return view;
    }
 
    public void updateView() {
      // do something to update the fragment
    }
  }

FragmentPagerAdapter和FragmentStatePagerAdapter對(duì)Fragment的管理略有不同笼平,在詳細(xì)考察二者區(qū)別之前,我們通過兩種較為直觀的方式先感受下:

通過兩張圖片直觀的對(duì)比FragmentPagerAdapter和FragmentStatePagerAdapter的區(qū)別

說明:這兩張圖片來自于《Android權(quán)威編程指南》舔痪,原圖有3個(gè)Fragment寓调,我增加了1個(gè)Fragment,以及被調(diào)到的方法锄码。
FragmentPagerAdapter的Fragment管理:

image-11-4-FragmentPagerAdapter的fragment管理-方法調(diào)用

FragmentStatePageAdapter的Fragment管理:


image-11-3FragmentStatePagerAdapter的fragment管理-方法調(diào)用

詳細(xì)分析 adapter method和fragment lifecycle method 的調(diào)用情況

好啦夺英,感受完畢,我們需要探究其詳情滋捶,梳理adapter創(chuàng)建痛悯、銷毀Fragment的過程,過程中adapter method和fragment lifecycle method哪些被調(diào)到炬太,有哪些一樣灸蟆,有哪些不一樣。

最開始處于第0頁時(shí)亲族,adapter不僅為第0頁創(chuàng)建Fragment實(shí)例炒考,還為相鄰的第1頁創(chuàng)建了Fragment實(shí)例:

// 剛開始處在page0
D/Adapter (25946): getItem(0)
D/Fragment0(25946): newInstance(2015-09-10)  // 注釋:newInstance()調(diào)用了Fragment的構(gòu)造器方法,下同霎迫。
D/Adapter (25946): getItem(1)
D/Fragment1(25946): newInstance(Hello World, I'm li2.)
D/Fragment0(25946): onAttach()
D/Fragment0(25946): onCreate()
D/Fragment0(25946): onCreateView()
D/Fragment1(25946): onAttach()
D/Fragment1(25946): onCreate()
D/Fragment1(25946): onCreateView()

第1次從第0頁滑到第1頁斋枢,adapter同樣會(huì)為相鄰的第2頁創(chuàng)建Fragment實(shí)例;

// 第1次滑到page1
D/Adapter (25946): onPageSelected(1)
D/Adapter (25946): getItem(2)
D/Fragment2(25946): newInstance(true)
D/Fragment2(25946): onAttach()
D/Fragment2(25946): onCreate()
D/Fragment2(25946): onCreateView()

FragmentPagerAdapter和FragmentStatePagerAdapter齊聲說:吶知给,請(qǐng)主公貳放心瓤帚,屬下定會(huì)為您準(zhǔn)備好相鄰的下一頁視圖噠描姚!么么噠!
它倆對(duì)待下一頁的態(tài)度是相同的戈次,但對(duì)于上上頁轩勘,它倆做出了不一樣的事情:
FragmentPagerAdapter說:上上頁的實(shí)例還保留著,只是銷毀了它的視圖

// 第N次(N不等于1)向右滑動(dòng)選中page2
D/Adapter (25946): onPageSelected(2)
D/Adapter (25946): destroyItem(0)  // 銷毀page0的視圖
D/Fragment0(25946): onDestroyView()
D/Fragment3(25946): onCreateView()  // page3的Fragment實(shí)例仍保存在FragmentManager中怯邪,所以只需創(chuàng)建它的視圖

FragmentStatePagerAdapter說:上上頁的實(shí)例和視圖都被俺銷毀啦

// 第N次(N不等于1)向右滑選中page2
D/Adapter (27880): onPageSelected(2)
D/Adapter (27880): destroyItem(0)  // 銷毀page0的實(shí)例和視圖
D/Adapter (27880): getItem(3)  // 創(chuàng)建page3的Fragment
D/Fragment3(27880): newInstance()
D/Fragment0(27880): onDestroyView()
D/Fragment0(27880): onDestroy()
D/Fragment0(27880): onDetach()
D/Fragment3(27880): onAttach()
D/Fragment3(27880): onCreate()
D/Fragment3(27880): onCreateView()

Fragment getItem(int position)

// Return the Fragment associated with a specified position.
public abstract Fragment getItem(int position);

當(dāng)adapter需要一個(gè)指定位置的Fragment绊寻,并且這個(gè)Fragment不存在時(shí),getItem就被調(diào)到悬秉,返回一個(gè)Fragment實(shí)例給adapter澄步。
所以,有必要再次強(qiáng)調(diào)和泌,getItem是創(chuàng)建一個(gè)新的Fragment村缸,但是這個(gè)方法名可能會(huì)被誤認(rèn)為是返回一個(gè)已經(jīng)存在的Fragment
對(duì)于FragmentPagerAdapter武氓,當(dāng)每頁的Fragment被創(chuàng)建后梯皿,這個(gè)函數(shù)就不會(huì)被調(diào)到了。對(duì)于FragmentStatePagerAdapter聋丝,由于Fragment會(huì)被銷毀索烹,所以它仍會(huì)被調(diào)到工碾。
由于我們必須在getItem中實(shí)例化一個(gè)Fragment弱睦,所以當(dāng)getItem()被調(diào)用后,F(xiàn)ragment相應(yīng)的生命周期函數(shù)也就被調(diào)到了:

D/Adapter (25946): getItem(1)
D/Fragment1(25946): newInstance(Hello World, I'm li2.)  // newInstance()調(diào)用了Fragment的構(gòu)造器方法渊额;
D/Fragment1(25946): onAttach()
D/Fragment1(25946): onCreate()
D/Fragment1(25946): onCreateView()

void destroyItem(ViewGroup container, int position, Object object)

// Remove a page for the given position. 
public void FragmentPagerAdapter.destroyItem(ViewGroup container, int position, Object object) {
    mCurTransaction.detach((Fragment)object);
}

public void FragmentStatePagerAdapter.destroyItem(ViewGroup container, int position, Object object) {
    mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment));
    mFragments.set(position, null);
    mCurTransaction.remove(fragment);
}

銷毀指定位置的Fragment况木。從源碼中可以看出二者的區(qū)別,一個(gè)detach旬迹,一個(gè)remove火惊,這將調(diào)用到不同的Fragment生命周期函數(shù):

// 對(duì)于FragmentPagerAdapter
D/Adapter (25946): onPageSelected(2)
D/Adapter (25946): destroyItem(0)
D/Fragment0(25946): onDestroyView()  // 銷毀視圖

// 對(duì)于FragmentStatePagerAdapter
D/Adapter (27880): onPageSelected(2)
D/Adapter (27880): destroyItem(0)
D/Fragment0(27880): onDestroyView()  // 銷毀視圖
D/Fragment0(27880): onDestroy()  // 銷毀實(shí)例
D/Fragment0(27880): onDetach()

FragmentPagerAdapter和FragmentStatePagerAdapter對(duì)比總結(jié)

二者使用方法基本相同,唯一的區(qū)別就在卸載不再需要的fragment時(shí)奔垦,采用的處理方式不同:

  • 使用FragmentStatePagerAdapter會(huì)銷毀掉不需要的fragment屹耐。事務(wù)提交后,可將fragment從activity的FragmentManager中徹底移除椿猎。類名中的“state”表明:在銷毀fragment時(shí)惶岭,它會(huì)將其onSaveInstanceState(Bundle) 方法中的Bundle信息保存下來。用戶切換回原來的頁面后犯眠,保存的實(shí)例狀態(tài)可用于恢復(fù)生成新的fragment.
  • FragmentPagerAdapter的做法大不相同按灶。對(duì)于不再需要的fragment,F(xiàn)ragmentPagerAdapter則選擇調(diào)用事務(wù)的detach(Fragment) 方法筐咧,而非remove(Fragment)方法來處理它鸯旁。也就是說噪矛,F(xiàn)ragmentPagerAdapter只是銷毀了fragment的視圖,但仍將fragment實(shí)例保留在FragmentManager中铺罢。因此艇挨, FragmentPagerAdapter創(chuàng)建的fragment永遠(yuǎn)不會(huì)被銷毀。

(摘抄自《Android權(quán)威編程指南11.1.4》)

更新ViewPager中的Fragment

調(diào)用notifyDataSetChanged()時(shí)韭赘,2個(gè)adapter的方法的調(diào)用情況相同雷袋,當(dāng)前頁和相鄰的兩頁的getItemPosition都會(huì)被調(diào)用到

// Called when the host view is attempting to determine if an item's position has changed. Returns POSITION_UNCHANGED if the position of the given item has not changed or POSITION_NONE if the item is no longer present in the adapter.
public int getItemPosition(Object object) {
    return POSITION_UNCHANGED;
}

從網(wǎng)上找到的解決辦法是辞居,覆寫getItemPosition使其返POSITION_NONE楷怒,以觸發(fā)Fragment的銷毀和重建⊥咴睿可是這將導(dǎo)致Fragment頻繁的銷毀和重建鸠删,并不是最佳的方法。
后來我把注意力放在了入口參數(shù)object上贼陶,"representing an item", 實(shí)際上就是Fragment刃泡,只需要為Fragment提供一個(gè)更新view的public方法:

@Override
// To update fragment in ViewPager, we should override getItemPosition() method,
// in this method, we call the fragment's public updating method.
public int getItemPosition(Object object) {
    Log.d(TAG, "getItemPosition(" + object.getClass().getSimpleName() + ")");
    if (object instanceof Page0Fragment) {
        ((Page0Fragment) object).updateDate(mDate);
    } else if (object instanceof Page1Fragment) {
        ((Page1Fragment) object).updateContent(mContent);
    } else if (object instanceof Page2Fragment) {
        ((Page2Fragment) object).updateCheckedStatus(mChecked);
    } else if (...) {
    }
    return super.getItemPosition(object);
};

// 更新界面時(shí)方法的調(diào)用情況
// 當(dāng)前頁為0時(shí)
D/Adapter (21517): notifyDataSetChanged(+0)
D/Adapter (21517): getItemPosition(Page0Fragment)
D/Fragment0(21517): updateDate(2015-09-12)
D/Adapter (21517): getItemPosition(Page1Fragment)
D/Fragment1(21517): updateContent(Hello World, I am li2.)

// 當(dāng)前頁為1時(shí)
D/Adapter (21517): notifyDataSetChanged(+1)
D/Adapter (21517): getItemPosition(Page0Fragment)
D/Fragment0(21517): updateDate(2015-09-13)
D/Adapter (21517): getItemPosition(Page1Fragment)
D/Fragment1(21517): updateContent(Hello World, I am li2.)
D/Adapter (21517): getItemPosition(Page2Fragment)
D/Fragment2(21517): updateCheckedStatus(true)

在最開始調(diào)用notifyDataSetChanged試圖更新Fragment時(shí),我是這樣做的:用arraylist保存所有的Fragment碉怔,當(dāng)需要更新時(shí)烘贴,就從arraylist中取出Fragment,然后調(diào)用該Fragment的update方法撮胧。這種做法非常魚唇桨踪,當(dāng)時(shí)完全不懂得adapter的Fragment manager在替我管理所有的Fragment。而我只需要:

  • 覆寫getCount告訴adapter有幾個(gè)Fragment芹啥;
  • 覆寫getItem以實(shí)例化一個(gè)指定位置的Fragment返回給adapter锻离;
  • 覆寫getItemPosition,把入口參數(shù)強(qiáng)制轉(zhuǎn)型成自定義的Fragment墓怀,然后調(diào)用該Fragment的update方法以完成更新汽纠。

只需要覆寫這幾個(gè)adapter的方法,adapter會(huì)為你完成所有的管理工作傀履,不需要自己保存虱朵、維護(hù)Fragment

替換ViewPager中的Fragment

應(yīng)用場(chǎng)景可能是這樣钓账,比如有一組按鈕碴犬,Day/Month/Year,有一個(gè)包含幾個(gè)Fragment的ViewPager官扣。點(diǎn)擊不同的按鈕翅敌,需要秀出不同的Fragment。
具體怎么實(shí)現(xiàn)惕蹄,請(qǐng)參考下面的代碼:
github.com/li2/Update_Replace_Fragment_In_ViewPager/ContainerFragment.java

一些誤區(qū)

ViewPager.getChildCount() 返回的是當(dāng)前ViewPager所管理的沒有被銷毀視圖的Fragment蚯涮,并不是所有的Fragment治专。想要獲取所有的Fragment數(shù)量,應(yīng)該調(diào)用ViewPager.getAdapter().getCount().

一個(gè)Demo

為了總結(jié)ViewPager的用法遭顶,以及寫這篇筆記张峰,我寫了一個(gè)demo,你可以從這里獲取它的源碼 github.com/li2/

這一張gif圖片棒旗,演示了一個(gè)包含4個(gè)Fragment的ViewPager喘批,通過上面的date+-1 button、EditText铣揉、Checkbox來更新前3個(gè)Fragment的界面饶深;最后一個(gè)Fragment嵌套著2個(gè)Fragment,通過ToggleButton來切換逛拱。

image-update_fragment_in_viewpager_demo

這一張gif演示了切換ViewPager頁以及更新Fragment時(shí)敌厘,相關(guān)的方法調(diào)用。通過一個(gè)ScrollView和TextView展示出來朽合。

image-update_fragment_in_viewpager_withlog

參考

關(guān)于作者

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市需五,隨后出現(xiàn)的幾起案子鹉动,更是在濱河造成了極大的恐慌,老刑警劉巖宏邮,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件泽示,死亡現(xiàn)場(chǎng)離奇詭異缸血,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)械筛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門捎泻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人埋哟,你說我怎么就攤上這事笆豁。” “怎么了赤赊?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵闯狱,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我抛计,道長(zhǎng)扩氢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任爷辱,我火速辦了婚禮录豺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘饭弓。我一直安慰自己双饥,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布弟断。 她就那樣靜靜地躺著咏花,像睡著了一般。 火紅的嫁衣襯著肌膚如雪阀趴。 梳的紋絲不亂的頭發(fā)上昏翰,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音刘急,去河邊找鬼棚菊。 笑死,一個(gè)胖子當(dāng)著我的面吹牛叔汁,可吹牛的內(nèi)容都是我干的统求。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼据块,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼码邻!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起另假,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤像屋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后边篮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體己莺,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡奏甫,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了篇恒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扶檐。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖胁艰,靈堂內(nèi)的尸體忽然破棺而出款筑,到底是詐尸還是另有隱情,我是刑警寧澤腾么,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布奈梳,位于F島的核電站,受9級(jí)特大地震影響解虱,放射性物質(zhì)發(fā)生泄漏攘须。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一殴泰、第九天 我趴在偏房一處隱蔽的房頂上張望于宙。 院中可真熱鬧,春花似錦悍汛、人聲如沸捞魁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谱俭。三九已至,卻和暖如春宵蛀,著一層夾襖步出監(jiān)牢的瞬間昆著,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國打工术陶, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留凑懂,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓瞳别,卻偏偏與公主長(zhǎng)得像征候,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子祟敛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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