FragmentPagerAdapter的三個調(diào)用方法
為了解決不同ViewPage 和Fragment之間切換時所引起的問題作彤,F(xiàn)ragmentPagerAdapter提供了一套相當(dāng)簡單易用的調(diào)用方法膘魄,讓頁面切換變得簡單易用乌逐。
有三點必須注意:
首先,ViewPager必須是手指橫向滑動操作瓣距。
其次是黔帕,每個頁面都必須由獨的fragment class去繼承。它繼承自android.support.v4.view.PagerAdapter蹈丸,每頁都是一個Fragment成黄,并且所有的Fragment實例一直保存在Fragment manager中。所以它適用于少量固定的fragment逻杖,比如一組用于分頁顯示的標(biāo)簽奋岁。除了當(dāng)Fragment不可見時,它的視圖層(view hierarchy)有可能被銷毀外荸百,每頁的Fragment都會被保存在內(nèi)存中闻伶。
第三是耗用內(nèi)存的問題。在使用FragmentPagerAdapter ?時够话,F(xiàn)ragment對象會一直存留在內(nèi)存中蓝翰,所以當(dāng)有大量的顯示頁時,就不適合用FragmentPagerAdapter 了女嘲,F(xiàn)ragmentPagerAdapter ?適用于只有少數(shù)的page情況畜份,像選項卡。
讓我們看看調(diào)用方法:
?// 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
}
}
【ViewPager】
ViewPager 如其名所述欣尼,是負責(zé)翻頁的一個 View爆雹。準確說是一個 ViewGroup,包含多個 View 頁愕鼓,在手指橫向滑動屏幕時钙态,其負責(zé)對 View 進行切換。為了生成這些 View 頁菇晃,需要提供一個 PagerAdapter 來進行和數(shù)據(jù)綁定以及生成最終的 View 頁册倒。
setAdapter()
ViewPager 通過 setAdapter() 來建立與 PagerAdapter 的聯(lián)系。這個聯(lián)系是雙向的磺送,一方面剩失,ViewPager 會擁有 PagerAdapter 對象,從而可以在需要時調(diào)用 PagerAdapter 的方法册着;另一方面,ViewPager 會在 setAdapter() 中調(diào)用 PagerAdapter 的 registerDataSetObserver() 方法脾歧,注冊一個自己生成的 PagerObserver 對象甲捏,從而在 PagerAdapter 有所需要時(如 notifyDataSetChanged() 或 notifyDataSetInvalidated() 時),可以調(diào)用 Observer 的 onChanged() 或 onInvalidated() 方法鞭执,從而實現(xiàn) PagerAdapter 向 ViewPager 方向發(fā)送信息司顿。
dataSetChanged()
在 PagerObserver.onChanged()芒粹,以及 PagerObserver.onInvalide() 中被調(diào)用。因此當(dāng) PagerAdapter.notifyDataSetChanged() 被觸發(fā)時大溜,ViewPager.dataSetChanged() 也可以被觸發(fā)化漆。該函數(shù)將使用 getItemPosition() 的返回值來進行判斷,如果為 POSITION_UNCHANGED钦奋,則什么都不做座云;如果為 POSITION_NONE,則調(diào)用 PagerAdapter.destroyItem() 來去掉該對象付材,并設(shè)置為需要刷新 (needPopulate = true) 以便觸發(fā) PagerAdapter.instantiateItem() 來生成新的對象朦拖。
【PagerAdapter】
PageAdapter 是 ViewPager 的支持者,ViewPager 將調(diào)用它來取得所需顯示的頁厌衔,而 PageAdapter 也會在數(shù)據(jù)變化時璧帝,通知 ViewPager。這個類也是FragmentPagerAdapter 以及 FragmentStatePagerAdapter 的基類富寿。如果繼承自該類睬隶,至少需要實現(xiàn) instantiateItem(), destroyItem(), getCount() 以及 isViewFromObject()。
getItemPosition()
該函數(shù)用以返回給定對象的位置页徐,給定對象是由 instantiateItem() 的返回值苏潜。
在 ViewPager.dataSetChanged() 中將對該函數(shù)的返回值進行判斷,以決定是否最終觸發(fā) PagerAdapter.instantiateItem() 函數(shù)泞坦。
在 PagerAdapter 中的實現(xiàn)是直接傳回 POSITION_UNCHANGED窖贤。如果該函數(shù)不被重載,則會一直返回 POSITION_UNCHANGED贰锁,從而導(dǎo)致 ViewPager.dataSetChanged() 被調(diào)用時赃梧,認為不必觸發(fā) PagerAdapter.instantiateItem()。很多人因為沒有重載該函數(shù)豌熄,而導(dǎo)致調(diào)用
PagerAdapter.notifyDataSetChanged() 后授嘀,什么都沒有發(fā)生。
instantiateItem()
在每次 ViewPager 需要一個用以顯示的 Object 的時候锣险,該函數(shù)都會被 ViewPager.addNewItem() 調(diào)用蹄皱。
notifyDataSetChanged()
在數(shù)據(jù)集發(fā)生變化的時候,一般 Activity 會調(diào)用 PagerAdapter.notifyDataSetChanged()芯肤,以通知 PagerAdapter巷折,而 PagerAdapter 則會通知在自己這里注冊過的所有 DataSetObserver尤辱。其中之一就是在 ViewPager.setAdapter() 中注冊過的 PageObserver闽颇。PageObserver 則進而調(diào)用 ViewPager.dataSetChanged(),從而導(dǎo)致 ViewPager 開始觸發(fā)更新其內(nèi)含 View 的操作下愈。
【FragmentPagerAdapter】
FragmentPagerAdapter 繼承自 PagerAdapter。相比通用的 PagerAdapter署拟,該類更專注于每一頁均為 Fragment 的情況婉宰。如文檔所述,該類內(nèi)的每一個生成的 Fragment 都將保存在內(nèi)存之中推穷,因此適用于那些相對靜態(tài)的頁心包,數(shù)量也比較少的那種;如果需要處理有很多頁馒铃,并且數(shù)據(jù)動態(tài)性較大蟹腾、占用內(nèi)存較多的情況,應(yīng)該使用FragmentStatePagerAdapter骗露。FragmentPagerAdapter 重載實現(xiàn)了幾個必須的函數(shù)岭佳,因此來自 PagerAdapter 的函數(shù),我們只需要實現(xiàn) getCount()萧锉,即可珊随。且,由于 FragmentPagerAdapter.instantiateItem() 的實現(xiàn)中柿隙,調(diào)用了一個新增的虛函數(shù) getItem()叶洞,因此,我們還至少需要實現(xiàn)一個 getItem()禀崖。因此衩辟,總體上來說,相對于繼承自 PagerAdapter波附,更方便一些艺晴。
getItem()
該類中新增的一個虛函數(shù)。函數(shù)的目的為生成新的 Fragment 對象掸屡。重載該函數(shù)時需要注意這一點封寞。在需要時,該函數(shù)將被 instantiateItem() 所調(diào)用仅财。
如果需要向 Fragment 對象傳遞相對靜態(tài)的數(shù)據(jù)時狈究,我們一般通過 Fragment.setArguments() 來進行,這部分代碼應(yīng)當(dāng)放到 getItem()盏求。它們只會在新生成 Fragment 對象時執(zhí)行一遍抖锥。
如果需要在生成 Fragment 對象后,將數(shù)據(jù)集里面一些動態(tài)的數(shù)據(jù)傳遞給該 Fragment碎罚,那么磅废,這部分代碼不適合放到 getItem() 中。因為當(dāng)數(shù)據(jù)集發(fā)生變化時荆烈,往往對應(yīng)的 Fragment 已經(jīng)生成还蹲,如果傳遞數(shù)據(jù)部分代碼放到了 getItem() 中,這部分代碼將不會被調(diào)用。這也是為什么很多人發(fā)現(xiàn)調(diào)用 PagerAdapter.notifyDataSetChanged() 后谜喊,getItem() 沒有被調(diào)用的一個原因。
instantiateItem()
函數(shù)中判斷一下要生成的 Fragment 是否已經(jīng)生成過了倦始,如果生成過了斗遏,就使用舊的,舊的將被 Fragment.attach()鞋邑;如果沒有诵次,就調(diào)用 getItem() 生成一個新的,新的對象將被 FragmentTransation.add()枚碗。
FragmentPagerAdapter 會將所有生成的 Fragment 對象通過 FragmentManager 保存起來備用逾一,以后需要該 Fragment 時,都會從 FragmentManager 讀取肮雨,而不會再次調(diào)用 getItem() 方法遵堵。
如果需要在生成 Fragment 對象后,將數(shù)據(jù)集中的一些數(shù)據(jù)傳遞給該 Fragment怨规,這部分代碼應(yīng)該放到這個函數(shù)的重載里陌宿。在我們繼承的子類中,重載該函數(shù)波丰,并調(diào)用 FragmentPagerAdapter.instantiateItem() 取得該函數(shù)返回 Fragment 對象壳坪,然后,我們該 Fragment 對象中對應(yīng)的方法掰烟,將數(shù)據(jù)傳遞過去爽蝴,然后返回該對象。
否則纫骑,如果將這部分傳遞數(shù)據(jù)的代碼放到 getItem()中蝎亚,在 PagerAdapter.notifyDataSetChanged() 后,這部分數(shù)據(jù)設(shè)置代碼將不會被調(diào)用惧磺。
destroyItem()
該函數(shù)被調(diào)用后颖对,會對 Fragment 進行 FragmentTransaction.detach()。這里不是 remove()磨隘,只是 detach()缤底,因此 Fragment 還在 FragmentManager 管理中,F(xiàn)ragment 所占用的資源不會被釋放番捂。
【FragmentStatePagerAdapter】
FragmentStatePagerAdapter 和前面的 FragmentPagerAdapter 一樣个唧,是繼承子 PagerAdapter。但是设预,和 FragmentPagerAdapter 不一樣的是徙歼,正如其類名中的 'State' 所表明的含義一樣,該 PagerAdapter 的實現(xiàn)將只保留當(dāng)前頁面,當(dāng)頁面離開視線后魄梯,就會被消除桨螺,釋放其資源;而在頁面需要顯示時酿秸,生成新的頁面(就像 ListView 的實現(xiàn)一樣)灭翔。這么實現(xiàn)的好處就是當(dāng)擁有大量的頁面時,不必在內(nèi)存中占用大量的內(nèi)存辣苏。
getItem()
一個該類中新增的虛函數(shù)肝箱。
函數(shù)的目的為生成新的 Fragment 對象。
Fragment.setArguments() 這種只會在新建 Fragment 時執(zhí)行一次的參數(shù)傳遞代碼稀蟋,可以放在這里煌张。
由于 FragmentStatePagerAdapter.instantiateItem() 在大多數(shù)情況下,都將調(diào)用 getItem() 來生成新的對象退客,因此如果在該函數(shù)中放置與數(shù)據(jù)集相關(guān)的 setter 代碼骏融,基本上都可以在 instantiateItem() 被調(diào)用時執(zhí)行,但這和設(shè)計意圖不符井辜。畢竟還有部分可能是不會調(diào)用 getItem() 的绎谦。因此這部分代碼應(yīng)該放到 instantiateItem() 中。
instantiateItem()
除非碰到 FragmentManager 剛好從 SavedState 中恢復(fù)了對應(yīng)的 Fragment 的情況外粥脚,該函數(shù)將會調(diào)用 getItem() 函數(shù)窃肠,生成新的 Fragment 對象。新的對象將被 FragmentTransaction.add()刷允。
FragmentStatePagerAdapter 就是通過這種方式冤留,每次都創(chuàng)建一個新的 Fragment,而在不用后就立刻釋放其資源树灶,來達到節(jié)省內(nèi)存占用的目的的纤怒。
destroyItem()
將 Fragment 移除,即調(diào)用 FragmentTransaction.remove()天通,并釋放其資源泊窘。