FragmentPagerAdapter與FragmentStatePagerAdapter區別

fb0122發表於2018-04-11

【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() 呼叫。

    • 官方API定義:

      >Create the page for the given position. The adapter is responsible for adding the view to the container given here, although it only must ensure this is done by the time it returns from finishUpdate(ViewGroup).

  • notifyDataSetChanged()

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

【FragmentPagerAdapter】

FragmentPagerAdapter 繼承自 PagerAdapter。相比通用的 PagerAdapter,該類更專注於每一頁均為 Fragment 的情況。如文件所述,該類內的每一個生成的 Fragment 都將儲存在記憶體之中,因此適用於那些相對靜態的頁,數量也比較少的那種;如果需要處理有很多頁,並且資料動態性較大、佔用記憶體較多的情況,應該使用FragmentStatePagerAdapter。

  • getItem()
    • 該類中新增的一個虛擬函式。函式的目的為生成新的 Fragment 物件。過載該函式時需要注意這一點。在需要時,該函式將被 instantiateItem() 所呼叫。
  • 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(),並釋放其資源。

相關文章