ViewPager
ViewPager 是android support中的類。
ViewPager直接繼承自ViewGroup類,它是一個容器類,可以在其中添加其他的View。
ViewPager需要一個PagerAdapter適配器,來為他提供數據。
ViewPager經常和Fragment一起使用,并且提供了專門的FragmentPagerAdapter和FragmentStatePagerAdapter類
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() 來生成新的對象。
setOffscreenPageLimit(int limit)
表示viewpage除了當前顯示的頁面外,左右個預加載的頁面個數,也就是 為limit=2時表示當前算上自己一共加載了5個頁面。這里是預加載不等同于Adapter中的緩存。
PagerAdapter
基礎使用
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 的操作。
總結
PagerAdapter需要注意notifyDataSetChanged()的實現和使用,可自定義一些參數,以及重寫getItemPosition()notifyDataSetChanged()方法達到刷新的需求。
FragmentPagerAdapter
FragmentPagerAdapter繼承 PagerAdapte的專注于Fragment的適配器,Fragment會保存在內存中(通過destroyItem源碼可以得知),所以當ViewPager中Fragment的數量較多時,建議使用FragmentStatePagerAdapter。
基本用法
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 所占用的資源不會被釋放。
總結
由于FragmentPagerAdapter?instantiateItem()方法在有緩存的時候總是會去拿緩存數據,所以調用notifyDataSetChanged()數據不會發生變化,我們要使數據發生變化,則必須重寫instantiateItem()和getItem()函數,使用我們自己的重載邏輯。
FragmentStatePagerAdapter
FragmentPagerAdapter繼承 PagerAdapte的專注于Fragment的適配器,由于instantiateItem()?和destroyItem()內部實現不同,功能也有區別,它只保留當前頁面,當頁面離開時,就會被消除,釋放其資源。在頁面需要顯示時,生成新的頁面。
基本用法
instantiateItem()
除非碰到 FragmentManager 剛好從 SavedState 中恢復了對應的 Fragment 的情況外,該函數將會調用 getItem() 函數,生成新的 Fragment 對象。新的對象將被 FragmentTransaction.add()。
FragmentStatePagerAdapter 就是通過這種方式,每次都創建一個新的 Fragment,而在不用后就立刻釋放其資源,來達到節省內存占用的目的的。
destroyItem()
將 Fragment 移除,即調用 FragmentTransaction.remove(),并釋放其資源。
總結
FragmentStatePagerAdapter,由于instantiateItem()和destroyItem(),特性導致用戶體驗不好,我們可以重寫instantiateItem()和destroyItem()、notifyDataSetChanged()、和自定義數組管理Fragment和狀態,達到緩存幾個,調用notifyDataSetChanged()刷新時清空緩存的效果。