FragmentPagerAdapter的三個(gè)調(diào)用方法
為了解決不同ViewPage 和Fragment之間切換時(shí)所引起的問(wèn)題,F(xiàn)ragmentPagerAdapter提供了一套相當(dāng)簡(jiǎn)單易用的調(diào)用方法,讓頁(yè)面切換變得簡(jiǎn)單易用。
有三點(diǎn)必須注意:
首先,ViewPager必須是手指橫向滑動(dòng)操作。
其次是,每個(gè)頁(yè)面都必須由獨(dú)的fragment class去繼承。它繼承自android.support.v4.view.PagerAdapter,每頁(yè)都是一個(gè)Fragment,并且所有的Fragment實(shí)例一直保存在Fragment manager中。所以它適用于少量固定的fragment,比如一組用于分頁(yè)顯示的標(biāo)簽。除了當(dāng)Fragment不可見(jiàn)時(shí),它的視圖層(view hierarchy)有可能被銷毀外,每頁(yè)的Fragment都會(huì)被保存在內(nèi)存中。
第三是耗用內(nèi)存的問(wèn)題。在使用FragmentPagerAdapter ?時(shí),F(xiàn)ragment對(duì)象會(huì)一直存留在內(nèi)存中,所以當(dāng)有大量的顯示頁(yè)時(shí),就不適合用FragmentPagerAdapter 了,F(xiàn)ragmentPagerAdapter ?適用于只有少數(shù)的page情況,像選項(xiàng)卡。
讓我們看看調(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 如其名所述,是負(fù)責(zé)翻頁(yè)的一個(gè) View。準(zhǔn)確說(shuō)是一個(gè) ViewGroup,包含多個(gè) View 頁(yè),在手指橫向滑動(dòng)屏幕時(shí),其負(fù)責(zé)對(duì) View 進(jìn)行切換。為了生成這些 View 頁(yè),需要提供一個(gè) PagerAdapter 來(lái)進(jìn)行和數(shù)據(jù)綁定以及生成最終的 View 頁(yè)。
setAdapter()
ViewPager 通過(guò) setAdapter() 來(lái)建立與 PagerAdapter 的聯(lián)系。這個(gè)聯(lián)系是雙向的,一方面,ViewPager 會(huì)擁有 PagerAdapter 對(duì)象,從而可以在需要時(shí)調(diào)用 PagerAdapter 的方法;另一方面,ViewPager 會(huì)在 setAdapter() 中調(diào)用 PagerAdapter 的 registerDataSetObserver() 方法,注冊(cè)一個(gè)自己生成的 PagerObserver 對(duì)象,從而在 PagerAdapter 有所需要時(shí)(如 notifyDataSetChanged() 或 notifyDataSetInvalidated() 時(shí)),可以調(diào)用 Observer 的 onChanged() 或 onInvalidated() 方法,從而實(shí)現(xiàn) PagerAdapter 向 ViewPager 方向發(fā)送信息。
dataSetChanged()
在 PagerObserver.onChanged(),以及 PagerObserver.onInvalide() 中被調(diào)用。因此當(dāng) PagerAdapter.notifyDataSetChanged() 被觸發(fā)時(shí),ViewPager.dataSetChanged() 也可以被觸發(fā)。該函數(shù)將使用 getItemPosition() 的返回值來(lái)進(jìn)行判斷,如果為 POSITION_UNCHANGED,則什么都不做;如果為 POSITION_NONE,則調(diào)用 PagerAdapter.destroyItem() 來(lái)去掉該對(duì)象,并設(shè)置為需要刷新 (needPopulate = true) 以便觸發(fā) PagerAdapter.instantiateItem() 來(lái)生成新的對(duì)象。
【PagerAdapter】
PageAdapter 是 ViewPager 的支持者,ViewPager 將調(diào)用它來(lái)取得所需顯示的頁(yè),而 PageAdapter 也會(huì)在數(shù)據(jù)變化時(shí),通知 ViewPager。這個(gè)類也是FragmentPagerAdapter 以及 FragmentStatePagerAdapter 的基類。如果繼承自該類,至少需要實(shí)現(xiàn) instantiateItem(), destroyItem(), getCount() 以及 isViewFromObject()。
getItemPosition()
該函數(shù)用以返回給定對(duì)象的位置,給定對(duì)象是由 instantiateItem() 的返回值。
在 ViewPager.dataSetChanged() 中將對(duì)該函數(shù)的返回值進(jìn)行判斷,以決定是否最終觸發(fā) PagerAdapter.instantiateItem() 函數(shù)。
在 PagerAdapter 中的實(shí)現(xiàn)是直接傳回 POSITION_UNCHANGED。如果該函數(shù)不被重載,則會(huì)一直返回 POSITION_UNCHANGED,從而導(dǎo)致 ViewPager.dataSetChanged() 被調(diào)用時(shí),認(rèn)為不必觸發(fā) PagerAdapter.instantiateItem()。很多人因?yàn)闆](méi)有重載該函數(shù),而導(dǎo)致調(diào)用
PagerAdapter.notifyDataSetChanged() 后,什么都沒(méi)有發(fā)生。
instantiateItem()
在每次 ViewPager 需要一個(gè)用以顯示的 Object 的時(shí)候,該函數(shù)都會(huì)被 ViewPager.addNewItem() 調(diào)用。
notifyDataSetChanged()
在數(shù)據(jù)集發(fā)生變化的時(shí)候,一般 Activity 會(huì)調(diào)用 PagerAdapter.notifyDataSetChanged(),以通知 PagerAdapter,而 PagerAdapter 則會(huì)通知在自己這里注冊(cè)過(guò)的所有 DataSetObserver。其中之一就是在 ViewPager.setAdapter() 中注冊(cè)過(guò)的 PageObserver。PageObserver 則進(jìn)而調(diào)用 ViewPager.dataSetChanged(),從而導(dǎo)致 ViewPager 開(kāi)始觸發(fā)更新其內(nèi)含 View 的操作。
【FragmentPagerAdapter】
FragmentPagerAdapter 繼承自 PagerAdapter。相比通用的 PagerAdapter,該類更專注于每一頁(yè)均為 Fragment 的情況。如文檔所述,該類內(nèi)的每一個(gè)生成的 Fragment 都將保存在內(nèi)存之中,因此適用于那些相對(duì)靜態(tài)的頁(yè),數(shù)量也比較少的那種;如果需要處理有很多頁(yè),并且數(shù)據(jù)動(dòng)態(tài)性較大、占用內(nèi)存較多的情況,應(yīng)該使用FragmentStatePagerAdapter。FragmentPagerAdapter 重載實(shí)現(xiàn)了幾個(gè)必須的函數(shù),因此來(lái)自 PagerAdapter 的函數(shù),我們只需要實(shí)現(xiàn) getCount(),即可。且,由于 FragmentPagerAdapter.instantiateItem() 的實(shí)現(xiàn)中,調(diào)用了一個(gè)新增的虛函數(shù) getItem(),因此,我們還至少需要實(shí)現(xiàn)一個(gè) getItem()。因此,總體上來(lái)說(shuō),相對(duì)于繼承自 PagerAdapter,更方便一些。
getItem()
該類中新增的一個(gè)虛函數(shù)。函數(shù)的目的為生成新的 Fragment 對(duì)象。重載該函數(shù)時(shí)需要注意這一點(diǎn)。在需要時(shí),該函數(shù)將被 instantiateItem() 所調(diào)用。
如果需要向 Fragment 對(duì)象傳遞相對(duì)靜態(tài)的數(shù)據(jù)時(shí),我們一般通過(guò) Fragment.setArguments() 來(lái)進(jìn)行,這部分代碼應(yīng)當(dāng)放到 getItem()。它們只會(huì)在新生成 Fragment 對(duì)象時(shí)執(zhí)行一遍。
如果需要在生成 Fragment 對(duì)象后,將數(shù)據(jù)集里面一些動(dòng)態(tài)的數(shù)據(jù)傳遞給該 Fragment,那么,這部分代碼不適合放到 getItem() 中。因?yàn)楫?dāng)數(shù)據(jù)集發(fā)生變化時(shí),往往對(duì)應(yīng)的 Fragment 已經(jīng)生成,如果傳遞數(shù)據(jù)部分代碼放到了 getItem() 中,這部分代碼將不會(huì)被調(diào)用。這也是為什么很多人發(fā)現(xiàn)調(diào)用 PagerAdapter.notifyDataSetChanged() 后,getItem() 沒(méi)有被調(diào)用的一個(gè)原因。
instantiateItem()
函數(shù)中判斷一下要生成的 Fragment 是否已經(jīng)生成過(guò)了,如果生成過(guò)了,就使用舊的,舊的將被 Fragment.attach();如果沒(méi)有,就調(diào)用 getItem() 生成一個(gè)新的,新的對(duì)象將被 FragmentTransation.add()。
FragmentPagerAdapter 會(huì)將所有生成的 Fragment 對(duì)象通過(guò) FragmentManager 保存起來(lái)備用,以后需要該 Fragment 時(shí),都會(huì)從 FragmentManager 讀取,而不會(huì)再次調(diào)用 getItem() 方法。
如果需要在生成 Fragment 對(duì)象后,將數(shù)據(jù)集中的一些數(shù)據(jù)傳遞給該 Fragment,這部分代碼應(yīng)該放到這個(gè)函數(shù)的重載里。在我們繼承的子類中,重載該函數(shù),并調(diào)用 FragmentPagerAdapter.instantiateItem() 取得該函數(shù)返回 Fragment 對(duì)象,然后,我們?cè)?Fragment 對(duì)象中對(duì)應(yīng)的方法,將數(shù)據(jù)傳遞過(guò)去,然后返回該對(duì)象。
否則,如果將這部分傳遞數(shù)據(jù)的代碼放到 getItem()中,在 PagerAdapter.notifyDataSetChanged() 后,這部分?jǐn)?shù)據(jù)設(shè)置代碼將不會(huì)被調(diào)用。
destroyItem()
該函數(shù)被調(diào)用后,會(huì)對(duì) Fragment 進(jìn)行 FragmentTransaction.detach()。這里不是 remove(),只是 detach(),因此 Fragment 還在 FragmentManager 管理中,F(xiàn)ragment 所占用的資源不會(huì)被釋放。
【FragmentStatePagerAdapter】
FragmentStatePagerAdapter 和前面的 FragmentPagerAdapter 一樣,是繼承子 PagerAdapter。但是,和 FragmentPagerAdapter 不一樣的是,正如其類名中的 'State' 所表明的含義一樣,該 PagerAdapter 的實(shí)現(xiàn)將只保留當(dāng)前頁(yè)面,當(dāng)頁(yè)面離開(kāi)視線后,就會(huì)被消除,釋放其資源;而在頁(yè)面需要顯示時(shí),生成新的頁(yè)面(就像 ListView 的實(shí)現(xiàn)一樣)。這么實(shí)現(xiàn)的好處就是當(dāng)擁有大量的頁(yè)面時(shí),不必在內(nèi)存中占用大量的內(nèi)存。
getItem()
一個(gè)該類中新增的虛函數(shù)。
函數(shù)的目的為生成新的 Fragment 對(duì)象。
Fragment.setArguments() 這種只會(huì)在新建 Fragment 時(shí)執(zhí)行一次的參數(shù)傳遞代碼,可以放在這里。
由于 FragmentStatePagerAdapter.instantiateItem() 在大多數(shù)情況下,都將調(diào)用 getItem() 來(lái)生成新的對(duì)象,因此如果在該函數(shù)中放置與數(shù)據(jù)集相關(guān)的 setter 代碼,基本上都可以在 instantiateItem() 被調(diào)用時(shí)執(zhí)行,但這和設(shè)計(jì)意圖不符。畢竟還有部分可能是不會(huì)調(diào)用 getItem() 的。因此這部分代碼應(yīng)該放到 instantiateItem() 中。
instantiateItem()
除非碰到 FragmentManager 剛好從 SavedState 中恢復(fù)了對(duì)應(yīng)的 Fragment 的情況外,該函數(shù)將會(huì)調(diào)用 getItem() 函數(shù),生成新的 Fragment 對(duì)象。新的對(duì)象將被 FragmentTransaction.add()。
FragmentStatePagerAdapter 就是通過(guò)這種方式,每次都創(chuàng)建一個(gè)新的 Fragment,而在不用后就立刻釋放其資源,來(lái)達(dá)到節(jié)省內(nèi)存占用的目的的。
destroyItem()
將 Fragment 移除,即調(diào)用 FragmentTransaction.remove(),并釋放其資源。