研磨FragmentPagerAdapter的方法

FragmentPagerAdapter的三個調用方法

為了解決不同ViewPage 和Fragment之間切換時所引起的問題,FragmentPagerAdapter提供了一套相當簡單易用的調用方法,讓頁面切換變得簡單易用。

有三點必須注意:

首先,ViewPager必須是手指橫向滑動操作。

其次是,每個頁面都必須由獨的fragment class去繼承。它繼承自android.support.v4.view.PagerAdapter,每頁都是一個Fragment,并且所有的Fragment實例一直保存在Fragment manager中。所以它適用于少量固定的fragment,比如一組用于分頁顯示的標簽。除了當Fragment不可見時,它的視圖層(view hierarchy)有可能被銷毀外,每頁的Fragment都會被保存在內存中。

第三是耗用內存的問題。在使用FragmentPagerAdapter ?時,Fragment對象會一直存留在內存中,所以當有大量的顯示頁時,就不適合用FragmentPagerAdapter 了,FragmentPagerAdapter ?適用于只有少數的page情況,像選項卡。

讓我們看看調用方法:


?// 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 如其名所述,是負責翻頁的一個 View。準確說是一個 ViewGroup,包含多個 View 頁,在手指橫向滑動屏幕時,其負責對 View 進行切換。為了生成這些 View 頁,需要提供一個 PagerAdapter 來進行和數據綁定以及生成最終的 View 頁。

setAdapter()

ViewPager 通過 setAdapter() 來建立與 PagerAdapter 的聯系。這個聯系是雙向的,一方面,ViewPager 會擁有 PagerAdapter 對象,從而可以在需要時調用 PagerAdapter 的方法;另一方面,ViewPager 會在 setAdapter() 中調用 PagerAdapter 的 registerDataSetObserver() 方法,注冊一個自己生成的 PagerObserver 對象,從而在 PagerAdapter 有所需要時(如 notifyDataSetChanged() 或 notifyDataSetInvalidated() 時),可以調用 Observer 的 onChanged() 或 onInvalidated() 方法,從而實現 PagerAdapter 向 ViewPager 方向發送信息。

dataSetChanged()

在 PagerObserver.onChanged(),以及 PagerObserver.onInvalide() 中被調用。因此當 PagerAdapter.notifyDataSetChanged() 被觸發時,ViewPager.dataSetChanged() 也可以被觸發。該函數將使用 getItemPosition() 的返回值來進行判斷,如果為 POSITION_UNCHANGED,則什么都不做;如果為 POSITION_NONE,則調用 PagerAdapter.destroyItem() 來去掉該對象,并設置為需要刷新 (needPopulate = true) 以便觸發 PagerAdapter.instantiateItem() 來生成新的對象。

【PagerAdapter】

PageAdapter 是 ViewPager 的支持者,ViewPager 將調用它來取得所需顯示的頁,而 PageAdapter 也會在數據變化時,通知 ViewPager。這個類也是FragmentPagerAdapter 以及 FragmentStatePagerAdapter 的基類。如果繼承自該類,至少需要實現 instantiateItem(), destroyItem(), getCount() 以及 isViewFromObject()。


getItemPosition()

該函數用以返回給定對象的位置,給定對象是由 instantiateItem() 的返回值。

在 ViewPager.dataSetChanged() 中將對該函數的返回值進行判斷,以決定是否最終觸發 PagerAdapter.instantiateItem() 函數。

在 PagerAdapter 中的實現是直接傳回 POSITION_UNCHANGED。如果該函數不被重載,則會一直返回 POSITION_UNCHANGED,從而導致 ViewPager.dataSetChanged() 被調用時,認為不必觸發 PagerAdapter.instantiateItem()。很多人因為沒有重載該函數,而導致調用

PagerAdapter.notifyDataSetChanged() 后,什么都沒有發生。

instantiateItem()

在每次 ViewPager 需要一個用以顯示的 Object 的時候,該函數都會被 ViewPager.addNewItem() 調用。

notifyDataSetChanged()

在數據集發生變化的時候,一般 Activity 會調用 PagerAdapter.notifyDataSetChanged(),以通知 PagerAdapter,而 PagerAdapter 則會通知在自己這里注冊過的所有 DataSetObserver。其中之一就是在 ViewPager.setAdapter() 中注冊過的 PageObserver。PageObserver 則進而調用 ViewPager.dataSetChanged(),從而導致 ViewPager 開始觸發更新其內含 View 的操作。

【FragmentPagerAdapter】

FragmentPagerAdapter 繼承自 PagerAdapter。相比通用的 PagerAdapter,該類更專注于每一頁均為 Fragment 的情況。如文檔所述,該類內的每一個生成的 Fragment 都將保存在內存之中,因此適用于那些相對靜態的頁,數量也比較少的那種;如果需要處理有很多頁,并且數據動態性較大、占用內存較多的情況,應該使用FragmentStatePagerAdapter。FragmentPagerAdapter 重載實現了幾個必須的函數,因此來自 PagerAdapter 的函數,我們只需要實現 getCount(),即可。且,由于 FragmentPagerAdapter.instantiateItem() 的實現中,調用了一個新增的虛函數 getItem(),因此,我們還至少需要實現一個 getItem()。因此,總體上來說,相對于繼承自 PagerAdapter,更方便一些。

getItem()

該類中新增的一個虛函數。函數的目的為生成新的 Fragment 對象。重載該函數時需要注意這一點。在需要時,該函數將被 instantiateItem() 所調用。

如果需要向 Fragment 對象傳遞相對靜態的數據時,我們一般通過 Fragment.setArguments() 來進行,這部分代碼應當放到 getItem()。它們只會在新生成 Fragment 對象時執行一遍。

如果需要在生成 Fragment 對象后,將數據集里面一些動態的數據傳遞給該 Fragment,那么,這部分代碼不適合放到 getItem() 中。因為當數據集發生變化時,往往對應的 Fragment 已經生成,如果傳遞數據部分代碼放到了 getItem() 中,這部分代碼將不會被調用。這也是為什么很多人發現調用 PagerAdapter.notifyDataSetChanged() 后,getItem() 沒有被調用的一個原因。

instantiateItem()

函數中判斷一下要生成的 Fragment 是否已經生成過了,如果生成過了,就使用舊的,舊的將被 Fragment.attach();如果沒有,就調用 getItem() 生成一個新的,新的對象將被 FragmentTransation.add()。

FragmentPagerAdapter 會將所有生成的 Fragment 對象通過 FragmentManager 保存起來備用,以后需要該 Fragment 時,都會從 FragmentManager 讀取,而不會再次調用 getItem() 方法。

如果需要在生成 Fragment 對象后,將數據集中的一些數據傳遞給該 Fragment,這部分代碼應該放到這個函數的重載里。在我們繼承的子類中,重載該函數,并調用 FragmentPagerAdapter.instantiateItem() 取得該函數返回 Fragment 對象,然后,我們該 Fragment 對象中對應的方法,將數據傳遞過去,然后返回該對象。

否則,如果將這部分傳遞數據的代碼放到 getItem()中,在 PagerAdapter.notifyDataSetChanged() 后,這部分數據設置代碼將不會被調用。

destroyItem()

該函數被調用后,會對 Fragment 進行 FragmentTransaction.detach()。這里不是 remove(),只是 detach(),因此 Fragment 還在 FragmentManager 管理中,Fragment 所占用的資源不會被釋放。

【FragmentStatePagerAdapter】

FragmentStatePagerAdapter 和前面的 FragmentPagerAdapter 一樣,是繼承子 PagerAdapter。但是,和 FragmentPagerAdapter 不一樣的是,正如其類名中的 'State' 所表明的含義一樣,該 PagerAdapter 的實現將只保留當前頁面,當頁面離開視線后,就會被消除,釋放其資源;而在頁面需要顯示時,生成新的頁面(就像 ListView 的實現一樣)。這么實現的好處就是當擁有大量的頁面時,不必在內存中占用大量的內存。

getItem()

一個該類中新增的虛函數。

函數的目的為生成新的 Fragment 對象。

Fragment.setArguments() 這種只會在新建 Fragment 時執行一次的參數傳遞代碼,可以放在這里。

由于 FragmentStatePagerAdapter.instantiateItem() 在大多數情況下,都將調用 getItem() 來生成新的對象,因此如果在該函數中放置與數據集相關的 setter 代碼,基本上都可以在 instantiateItem() 被調用時執行,但這和設計意圖不符。畢竟還有部分可能是不會調用 getItem() 的。因此這部分代碼應該放到 instantiateItem() 中。

instantiateItem()

除非碰到 FragmentManager 剛好從 SavedState 中恢復了對應的 Fragment 的情況外,該函數將會調用 getItem() 函數,生成新的 Fragment 對象。新的對象將被 FragmentTransaction.add()。

FragmentStatePagerAdapter 就是通過這種方式,每次都創建一個新的 Fragment,而在不用后就立刻釋放其資源,來達到節省內存占用的目的的。

destroyItem()

將 Fragment 移除,即調用 FragmentTransaction.remove(),并釋放其資源。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容