ViewPager刷新问题详解
原文鏈接:簡書diygreen,http://www.jianshu.com/p/266861496508
一、PagerAdapter介紹
先看效果圖
PagerAdapter簡介
ListView 大家應該都很熟悉吧!ListView 一般都需要一個 Adapter 來填充數(shù)據(jù),如 ArrayAdapter、SimpleAdapter。PagerAdapter 就是 ViewPager 的 Adapter,與 ListView 的 Adapter 作用一樣。
ViewPager->PageAdapter == ListView->BaseAdapter
先看下官方介紹
PageAdapter 繼承自 Object,繼承結構參考意義不大,那老實看文檔。文檔上沒有提供示例代碼,只是說了下要自定義 PageAdapter 需要實現(xiàn)下面四個方法:
- instantiateItem(ViewGroup container, int position)
該方法的功能是創(chuàng)建指定位置的頁面視圖。適配器有責任增加即將創(chuàng)建的 View 視圖到這里給定的 container 中,這是為了確保在 finishUpdate(viewGroup) 返回時 this is be done!
返回值:返回一個代表新增視圖頁面的 Object(Key),這里沒必要非要返回視圖本身,也可以這個頁面的其它容器。其實我的理解是可以代表當前頁面的任意值,只要你可以與你增加的 View 一一對應即可,比如 position 變量也可以做為 Key
- destroyItem(ViewGroup container, int position, Object object)
該方法的功能是移除一個給定位置的頁面。適配器有責任從容器中刪除這個視圖,這是為了確保在 finishUpdate(viewGroup) 返回時視圖能夠被移除
getCount():返回當前有效視圖的數(shù)量
isViewFromObject(View view, Object object)
該函數(shù)用來判斷 instantiateItem() 函數(shù)所返回來的 Key 與一個頁面視圖是否是代表的同一個視圖(即它倆是否是對應的,對應的表示同一個 View)
返回值:如果對應的是同一個View,返回 true,否則返回 false
上面對 PageAdapter 的四個抽象方法做了簡要說明,下面看看如何使用
簡單使用
mContentVP.setAdapter(new PagerAdapter() {@Overridepublic int getCount() {return dataList.size();}@Overridepublic boolean isViewFromObject(View view, Object object) {return view == object;}@Overridepublic Object instantiateItem(ViewGroup container, int position) {View view = View.inflate(SimpleDemoActivity.this, R.layout.item_vp_demopageradapter, null);TextView pageNumTV = (TextView) view.findViewById(R.id.tv_pagenum);pageNumTV.setText("DIY-PageNum-" + dataList.get(position));container.addView(view);return view;}@Overridepublic void destroyItem(ViewGroup container, int position, Object object) {container.removeView((View) object);}});可以看到實現(xiàn) PagerAdapter 與 BaseAdapter 很類似,只是 PagerAdapter 的 isViewFromObject() 與 instantiateItem() 方法需要好好理解下。這里為了簡化 PagerAdapter 的使用,我做了個簡單的封裝:
public abstract class APagerAdapter<T> extends PagerAdapter {protected LayoutInflater mInflater;protected List<T> mDataList;private SparseArray<View> mViewSparseArray;public APagerAdapter(Context context, List<T> dataList) {mInflater = LayoutInflater.from(context);mDataList = dataList;mViewSparseArray = new SparseArray<View>(dataList.size());}@Overridepublic int getCount() {if (mDataList == null) return 0;return mDataList.size();}@Overridepublic boolean isViewFromObject(View view, Object object) {return view == object;}@Overridepublic Object instantiateItem(ViewGroup container, int position) {View view = mViewSparseArray.get(position);if (view == null) {view = getView(position);mViewSparseArray.put(position, view);}container.addView(view);return view;}@Overridepublic void destroyItem(ViewGroup container, int position, Object object) {container.removeView(mViewSparseArray.get(position));}public abstract View getView(int position);}APagerAdapter 類模仿 ListView 的 BaseAdapter,抽象出一個 getView() 方法,在內(nèi)部使用 SparesArray 緩存所有顯示過的 View。這樣使用就很簡單了,繼承 APagerAdapter,實現(xiàn) getView() 方法即可(可以參考:DemoPagerAdapter.java)。
PagerAdapter 刷新的問題
在使用 ListView 的時候,我們往往習慣了更新 Adapter 的數(shù)據(jù)源,然后調(diào)用 Adapter 的 notifyDataSetChanged() 方法來刷新列表(有沒有點 MVC 的感覺)。
PagerAdapter 也有 notifyDataSetChanged() 方法,那我們按照這個流程來試試,看有沒有什么問題。(ListView 的示例就不在這里演示了,感興趣的可以自己去試試,非常簡單)
那么我的問題是:“ViewPager 的 PagerAdapter 在數(shù)據(jù)源更新后,能否自動刷新視圖?”
帶著問題,我們做一些實驗,下面實驗的思路是:修改數(shù)據(jù)源,然后通知 PagerAdapter 更新,查看視圖的變化。
實驗環(huán)境準備
看看實驗環(huán)境,上代碼:
private void initData() {// 數(shù)據(jù)源mDataList = new ArrayList<>(5);mDataList.add("Java");mDataList.add("Android");mDataList.add("C&C++");mDataList.add("OC");mDataList.add("Swift");// 很簡單的一個 PagerAdapterthis.mContentVP.setAdapter(mPagerAdapter = new PagerAdapter() {@Overridepublic int getCount() {return mDataList.size();}@Overridepublic boolean isViewFromObject(View view, Object object) {return view == object;}@Overridepublic Object instantiateItem(ViewGroup container, int position) {View view = View.inflate(SimpleDemoActivity.this, R.layout.item_vp_demopageradapter, null);TextView pageNumTV = (TextView) view.findViewById(R.id.tv_pagenum);pageNumTV.setText("DIY-PageNum-" + mDataList.get(position));container.addView(view);return view;}@Overridepublic void destroyItem(ViewGroup container, int position, Object object) {container.removeView((View) object);}}); }ViewPager 的 Item:item_vp_demopageradapter.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:gravity="center"><ImageView android:id="@+id/iv_img"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_margin="@dimen/activity_horizontal_margin"android:src="@mipmap/ic_launcher" /><!-- 用于顯示文本,數(shù)據(jù)更新體現(xiàn)在這里 --><TextView android:id="@+id/tv_pagenum"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_margin="@dimen/activity_horizontal_margin"android:textAppearance="?android:attr/textAppearanceLarge"android:text="DIY-Page-" /> </LinearLayout>很簡單的代碼,并且加了注釋,直接往下看實驗。
PagerAdapter 刷新實驗
1、更新數(shù)據(jù)源中的某項
對應代碼:
private void refresh() {mDataList.set(0, "更新數(shù)據(jù)源測試");mPagerAdapter.notifyDataSetChanged(); }問題描述:在演示動畫中可以看到,更新數(shù)據(jù)源之后視圖并沒有立即刷新,多滑動幾次再次回到更新的 Item 時才更新(這里先看問題,下面會細說)。
2、往數(shù)據(jù)源中添加數(shù)據(jù)
對應代碼:
private void add() {mDataList.add("這是新添加的Item");mPagerAdapter.notifyDataSetChanged(); }問題描述:沒什么問題,數(shù)據(jù)源添加數(shù)據(jù)后通知 PagerAdapter 刷新,ViewPager 中就多了一個 Item。
3、從數(shù)據(jù)源中刪除數(shù)據(jù)
private void delete() {mDataList.remove(0);mPagerAdapter.notifyDataSetChanged(); }問題描述:這個問題就較多了,首先,如果是刪除當前 Item,那么會看到?jīng)]有任何反應;其次,如果刪除的不是當前 Item,會發(fā)現(xiàn)出現(xiàn)了數(shù)據(jù)錯亂,并且后面有 Item 滑不過去,但是按住往后滑的時候可以看到后面的 Item。
4、將數(shù)據(jù)源清空
private void clean() {mDataList.clear();mPagerAdapter.notifyDataSetChanged(); }問題描述:從上面的動圖可以看到,清空數(shù)據(jù)源之后,會殘留一個 Item。
說明:先不要計較上面所寫的 PagerAdapter 是否有問題,這里只是想引出問題來,下面會針對 PagerAdapter、FragmentPagerAdapter 以及 FragmentStatePagerAdapter 來分析問題原因和給出解決方案。
二、PagerAdapter
從上面的實驗可以看出 ViewPager 不同于 ListView,如果單純的調(diào)用 ViewPager.getAdapter().notifyDataSetChanged() 方法(即 PagerAdapter 的 notifyDataSetChanged()方法)頁面并沒有刷新。
PagerAdapter 用于 ViewPager 的 Item 為普通 View的情況,這個相對簡單,所以最先介紹。
相信很多同學都搜過類似的問題 —— “PagerAdapter 的 notifyDataSetChanged() 不刷新?”。有的說這是 bug,有的則認為 Google 是特意這樣設計的,個人傾向后一種觀點(我覺得這是 Google 為了 ViewPager 性能考慮而設計的,畢竟 ViewPager 需要顯示“很多大的”視圖,而且要防止用戶滑動時覺得卡頓)。
ViewPager 刷新分析
先來了解下 ViewPager 的刷新過程:
1、刷新的起始
ViewPager 的刷新是從調(diào)用其 PagerAdapter 的 notifyDataSetChanged() 方法開始的,那先看看該方法的源碼(在源碼面前一切無所遁形…):
/*** This method should be called by the application if the data backing this adapter has changed* and associated views should update.*/ public void notifyDataSetChanged() {synchronized (this) {if (mViewPagerObserver != null) {mViewPagerObserver.onChanged();}}mObservable.notifyChanged(); }2、DataSetObservable 的 notifyChanged()
上面的方法中出現(xiàn)了兩個關鍵的成員變量:
private final DataSetObservable mObservable = new DataSetObservable(); private DataSetObserver mViewPagerObserver;觀察者模式,有沒有?先不著急分析這個是不是觀察者模式,來看看 mObservable.notifyChanged() 做了些什么工作:
/*** Invokes {@link DataSetObserver#onChanged} on each observer.* Called when the contents of the data set have changed. The recipient* will obtain the new contents the next time it queries the data set.*/ public void notifyChanged() {synchronized(mObservers) {// since onChanged() is implemented by the app, it could do anything, including// removing itself from {@link mObservers} - and that could cause problems if// an iterator is used on the ArrayList {@link mObservers}.// to avoid such problems, just march thru the list in the reverse order.for (int i = mObservers.size() - 1; i >= 0; i--) {mObservers.get(i).onChanged(); }} }notifyChanged() 方法中是很典型的觀察者模式中遍歷所有的 Observer,通知 變化發(fā)生了的代碼。代碼很簡單,那關鍵是這個 mObservers 包含哪些 Observer 呢?
3、DataSetObserver
直接從 mObservers 點進去你會發(fā)現(xiàn)這個:
protected final ArrayList<T> mObservers = new ArrayList<T>();-_-‘,這是個泛型,坑了!還好 DataSetObservable 的 notifyChanged() 的注釋中寫了這些 Observer 是 DataSetObserver。那去看看 DataSetObserver:
public abstract class DataSetObserver {/*** This method is called when the entire data set has changed,* most likely through a call to {@link Cursor#requery()} on a {@link Cursor}.*/public void onChanged() {// Do nothing}/*** This method is called when the entire data becomes invalid,* most likely through a call to {@link Cursor#deactivate()} or {@link Cursor#close()} on a* {@link Cursor}.*/public void onInvalidated() {// Do nothing} }一個抽象類,里面兩個空方法,這個好辦,找他的子類(AndroidStudio 中 將光標放到類名上,按 F4):
總算找到你了,就是用紅線框出來的那條,雙擊,定位過去。
4、PagerObserver 內(nèi)部類
PagerObserver 是 ViewPager 中的一個內(nèi)部類,實現(xiàn)也很簡單,就是調(diào)用了 ViewPager 中的 dataSetChanged() 方法,真正的關鍵來了。
private class PagerObserver extends DataSetObserver {@Overridepublic void onChanged() {dataSetChanged();}@Overridepublic void onInvalidated() {dataSetChanged();} }5、ViewPager 的 dataSetChanged()
這個方法的實現(xiàn)較長,里面的邏輯看上去挺復雜的,這里就不展示全部的源碼了,列下關鍵點:
... for (int i = 0; i < mItems.size(); i++) {final ItemInfo ii = mItems.get(i);final int newPos = mAdapter.getItemPosition(ii.object);if (newPos == PagerAdapter.POSITION_UNCHANGED) {continue;}if (newPos == PagerAdapter.POSITION_NONE) {...continue;}... } ...上面截取的代碼中 for 循環(huán)里面有兩個 continue 語句,這可能是比較關鍵的代碼,幸好不用我們繼續(xù)深入了,官方給出了解釋:
Called when the host view is attempting to determine if an item’s position has changed. Returns POSITION_UNCHANGED if the position of the given item has not changed or POSITION_NONE if the item is no longer present in the adapter.The default implementation assumes that items will never change position and always returns POSITION_UNCHANGED.
大致的意思是:
如果 Item 的位置如果沒有發(fā)生變化,則返回 POSITION_UNCHANGED。如果返回了 POSITION_NONE,表示該位置的 Item 已經(jīng)不存在了。默認的實現(xiàn)是假設 Item 的位置永遠不會發(fā)生變化,而返回 POSITION_UNCHANGED。(參考自:追溯源碼解決android疑難有關問題1-Viewpager之notifyDataSetChanged無刷新)
上面在源碼里面跟了一大圈是不是還是感覺沒有明朗,因為還有一個很關鍵的類 —— PagerAdapter 沒有介紹,再給點耐心,繼續(xù)。
6、PagerAdapter 的工作流程
其實就是 PagerAdapter 中方法的執(zhí)行順序,來看看 Leo8573 的分析(個人感覺基本說到位了,所以直接拷過來了):
PagerAdapter 作為 ViewPager 的適配器,無論 ViewPager 有多少頁,PagerAdapter 在初始化時也只初始化開始的2個 View,即調(diào)用2次instantiateItem 方法。而接下來每當 ViewPager 滑動時,PagerAdapter 都會調(diào)用 destroyItem 方法將距離該頁2個步幅以上的那個 View 銷毀,以此保證 PagerAdapter 最多只管轄3個 View,且當前 View 是3個中的中間一個,如果當前 View 缺少兩邊的 View,那么就 instantiateItem,如里有超過2個步幅的就 destroyItem。
簡易圖示:
*——+—+—+—+——
… 0 | 1 | 2 | 3 | 4 …
——+—+—+—+——
當前 View 為2號 View,所以 PagerAdapter 管轄1、2、3三個 View,接下來向左滑動–>
——+—+—+—+——
… 1 | 2 | 3 | 4 | 5 …
——+—+—+—+——
滑動后,當前 View 變?yōu)?號 View,PagerAdapter 會 destroyItem 0號View,instantiateItem 5號 View,所以 PagerAdapter 管轄2、3、4三個 View。(參考自:關于ViewPager的數(shù)據(jù)更新問題小結)
總結一下: Viewpager 的刷新過程是這樣的,在每次調(diào)用 PagerAdapter 的 notifyDataSetChanged() 方法時,都會激活 getItemPosition(Object object) 方法,該方法會遍歷 ViewPager 的所有 Item(由緩存的 Item 數(shù)量決定,默認為當前頁和其左右加起來共3頁,這個可以自行設定,但是至少會緩存2頁),為每個 Item 返回一個狀態(tài)值(POSITION_NONE/POSITION_UNCHANGED),如果是 POSITION_NONE,那么該 Item 會被 destroyItem(ViewGroup container, int position, Object object) 方法 remove 掉,然后重新加載,如果是 POSITION_UNCHANGED,就不會重新加載,默認是 POSITION_UNCHANGED,所以如果不重寫 getItemPosition(Object object),修改返回值,就無法看到 notifyDataSetChanged() 的刷新效果。
最簡單的解決方案
那就是直接一刀切:重寫 PagerAdapter 的 getItemPosition(Object object) 方法,將返回值固定為 POSITION_NONE。
先看看效果:
上代碼(PagerAdapterActivity.java):
@Override public int getItemPosition(Object object) {// 最簡單解決 notifyDataSetChanged() 頁面不刷新問題的方法return POSITION_NONE; }該方案的缺點:有個很明顯的缺陷,那就是會刷新所有的 Item,這將導致系統(tǒng)資源的浪費,所以這種方式不適合數(shù)據(jù)量較大的場景。
注意:
這種方式還有一個需要注意的地方,就是重寫 destoryItem() 方法:
最簡方案的優(yōu)化
這里提供一個思路,畢竟場景太多,相信大家理解了思路要實現(xiàn)就很簡單了,閑話不多說。
思路:在 instantiateItem() 方法中給每個 View 添加 tag(使用 setTag() 方法),然后在 getItemPosition() 方法中通過 View.getTag() 來判斷是否是需要刷新的頁面,是就返回 POSITION_NONE,否就返回 POSITION_UNCHANGED。 (參考自:ViewPager刷新單個頁面的方法)
注意:這里有一點要注意的是,當清空數(shù)據(jù)源的時候需要返回 POSITION_NONE,可用如下代碼:
if (mDataList != null && mDataList.size()==0) {return POSITION_NONE; }關于 PagerAdapter 的介紹就到這里了,雖然 FragmentPagerAdapter 與 FragmentStatePagerAdapter 都是繼承自 PagerAdapter。但是,這兩個是專門為以 Fragment 為 Item 的 ViewPager 所準備的,所以有其特殊性。且看下面的介紹。
三、FragmentPagerAdapter
簡介
上面通過使 getItemPosition() 方法返回 POSITION_NONE 到達數(shù)據(jù)源變化(也就是調(diào)用 notifyDataSetChanged())時,刷新視圖的目的。但是當我們使用 Fragment 作為 ViewPager 的 Item 時,就需要多考慮一些了,而且一般是使用 FragmentPagerAdapter 或者 FragmentStatePagerAdapter。
這里不展開討論 FragmentPagerAdapter 與 FragmentStatePagerAdapter 的異同和使用場景了,感興趣的可以看看這篇文章:FragmentPagerAdapter與FragmentStatePagerAdapter區(qū)別。
下面先來看看使用 FragmentPagerAdapter 時,如何在數(shù)據(jù)源發(fā)生變化時,刷新 Fragment 或者動態(tài)改變 Items 的數(shù)量。
方案:清除 FragmentManager 中緩存的 Fragment
先看效果:
實現(xiàn)上圖效果的關鍵代碼:
1、FPagerAdapter1Activity.java
private void refresh() {if (checkData()) return;mDataList.set(0, 7); // 修改數(shù)據(jù)源mPagerAdapter.updateData(mDataList); // 通知 Adapter 更新 }private void add() {mDataList.add(7);mPagerAdapter.updateData(mDataList); }private void delete() {if (checkData()) return;mDataList.remove(0);mPagerAdapter.updateData(mDataList); }private void clear() {if (checkData()) return;mDataList.clear();mPagerAdapter.updateData(mDataList); }2、FPagerAdapter1.java
public class FPagerAdapter1 extends FragmentPagerAdapter {private ArrayList<Fragment> mFragmentList;private FragmentManager mFragmentManager;public FPagerAdapter1(FragmentManager fm, List<Integer> types) {super(fm);this.mFragmentManager = fm;mFragmentList = new ArrayList<>();for (int i = 0, size = types.size(); i < size; i++) {mFragmentList.add(FragmentTest.instance(i));}setFragments(mFragmentList);}public void updateData(List<Integer> dataList) {ArrayList<Fragment> fragments = new ArrayList<>();for (int i = 0, size = dataList.size(); i < size; i++) {Log.e("FPagerAdapter1", dataList.get(i).toString());fragments.add(FragmentTest.instance(dataList.get(i)));}setFragments(fragments);}private void setFragments(ArrayList<Fragment> mFragmentList) {if(this.mFragmentList != null){FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();for(Fragment f:this.mFragmentList){fragmentTransaction.remove(f);}fragmentTransaction.commit();mFragmentManager.executePendingTransactions();}this.mFragmentList = mFragmentList;notifyDataSetChanged();}@Overridepublic int getCount() {return this.mFragmentList.size();}public int getItemPosition(Object object) {return POSITION_NONE;}@Overridepublic Fragment getItem(int position) {return mFragmentList.get(position);} }3、思路分析
上面的代碼思路很簡單,就是當數(shù)據(jù)源發(fā)生變化時,先將 FragmentManger 里面所有緩存的 Fragment 全部清除,然后重新創(chuàng)建,這樣達到刷新視圖的目的。
但是,這樣做有一個缺點,那就是會造成不必要的浪費,會影響性能。還有就是必須使用一個 List 緩存所有的 Fragment,這也得占用不少內(nèi)存…
思路挺簡單,這里不再贅述,那看看有沒有什么可以優(yōu)化的。
優(yōu)化:通過 Tag 獲取緩存的 Fragment
先看效果:
從上面的動圖上可以看到,更新某一個 Fragment 沒有問題,清空數(shù)據(jù)源的時候也沒有,添加當然也沒什么問題;請注意刪除的效果,雖然,目的 Fragment 確實從 ViewPager 中移除了,但是滑動后面的頁面會發(fā)現(xiàn)出現(xiàn)了數(shù)據(jù)錯亂。
分析一下優(yōu)化的思路:
先來了解 FragmentPagerAdapter 中是如何管理 Fragment 的,這里涉及到 FragmentPagerAdapter 中的 instantiateItem() 方法:
@Override public Object instantiateItem(ViewGroup container, int position) {if (mCurTransaction == null) {mCurTransaction = mFragmentManager.beginTransaction();}final long itemId = getItemId(position);// Do we already have this fragment?String name = makeFragmentName(container.getId(), itemId);Fragment fragment = mFragmentManager.findFragmentByTag(name);if (fragment != null) {if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);mCurTransaction.attach(fragment);} else {fragment = getItem(position);if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);mCurTransaction.add(container.getId(), fragment,makeFragmentName(container.getId(), itemId));}if (fragment != mCurrentPrimaryItem) {fragment.setMenuVisibility(false);fragment.setUserVisibleHint(false);}return fragment; }從源碼中可以看到在從 FragmentManager 中取出 Fragment 時調(diào)用了 findFragmentByTag() 方法,而這個 Tag 是由 makeFragmentName() 方法生成的。繼續(xù)往下可以看到每一個 Fragment 都打上了一個標簽(在 mCurTransaction.add() 方法中)。
也就是說是 FragmentManager 通過 Tag 找相應的 Fragment,從而達到緩存 Fragment 的目的。如果可以找到,就不會創(chuàng)建新的 Fragment,Fragment 的 onCreate()、onCreateView() 等方法都不會再次調(diào)用。
那優(yōu)化的思路就有了:
首先,需要緩存所有 Fragment 的 Tag,代碼如下:
該方法需要自行在 Fragment 中提供。
最后,對于動態(tài)改變 ViewPager 中 Fragment 的數(shù)量,如果是添加,那沒什么要注意的;但是刪除有點棘手。
在上面的動態(tài)上看到,刪除一個 Fragment 后會出現(xiàn)混亂,這里沒有進一步去研究了,這里僅提供一個示例供參考(這個示例代碼有問題,僅供參考)
public void remove(int position) {mDataList.remove(position);isDataSetChange = true;Fragment fragment = mFragmentManager.findFragmentByTag(mTagList.get(position));mTagList.remove(position);if (fragment == null) {notifyDataSetChanged();return;}FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();fragmentTransaction.remove(fragment);fragmentTransaction.commit();mFragmentManager.executePendingTransactions();notifyDataSetChanged(); }注意:
這個”優(yōu)化“示例,僅僅適用于在只需要更新某個 Fragment 的場景,關于動態(tài)刪除 Fragment,該”優(yōu)化“方案并不適用,也不推薦使用。
四、FragmentStatePagerAdapter
先看效果:
簡介
FragmentStatePagerAdapter 與 FragmentPagerAdapter 類似,這兩個類都繼承自 PagerAdapter。但是,和 FragmentPagerAdapter 不一樣的是,FragmentStatePagerAdapter 只保留當前頁面,當頁面離開視線后,就會被消除,釋放其資源;而在頁面需要顯示時,生成新的頁面(這和 ListView 的實現(xiàn)一樣)。這種方式的好處就是當擁有大量的頁面時,不必在內(nèi)存中占用大量的內(nèi)存。(參考自:FragmentPagerAdapter與FragmentStatePagerAdapter區(qū)別)
FragmentStatePagerAdapter 的實現(xiàn)與 FragmentPagerAdapter 有很大區(qū)別,如果照搬上述 FragmentPagerAdapter 刷新數(shù)據(jù)的方式,你會發(fā)現(xiàn)沒有什么問題(可以使用 FPagerAdapter11.java 測試)。
另一種思路
但是,我在項目中實際應用的時候(Fragment 比較復雜,里面有網(wǎng)絡任務等)出現(xiàn)了 IllegalStateException,發(fā)生在 ”fragmentTransaction.remove(f);“ 時。當時找了一些文章沒有解決該問題,考慮到項目中的 Fragment 里面邏輯過多,就換思路,沒有在這個上面繼續(xù)深究了。
如果,你也是這樣使用 FragmentStatePagerAdapter 來動態(tài)改變 ViewPager 中 Fragment,并且在 remove Fragment 時遇到了 IllegalStateException。那么,你可以考慮使用下面的方式,先看代碼(FSPagerAdapter .java):
public class FSPagerAdapter extends FragmentStatePagerAdapter {private ArrayList<Fragment> mFragmentList;public FSPagerAdapter(FragmentManager fm, List<Integer> types) {super(fm);updateData(types);}public void updateData(List<Integer> dataList) {ArrayList<Fragment> fragments = new ArrayList<>();for (int i = 0, size = dataList.size(); i < size; i++) {Log.e("FPagerAdapter1", dataList.get(i).toString());fragments.add(FragmentTest.instance(dataList.get(i)));}setFragmentList(fragments);}private void setFragmentList(ArrayList<Fragment> fragmentList) {if(this.mFragmentList != null){mFragmentList.clear();}this.mFragmentList = fragmentList;notifyDataSetChanged();}@Overridepublic int getCount() {return this.mFragmentList.size();}public int getItemPosition(Object object) {return POSITION_NONE;}@Overridepublic Fragment getItem(int position) {return mFragmentList.get(position);} }對應的測試 Activity 見 FSPagerAdapterActivity.java。
上面的代碼挺簡單,稍微解釋一下實現(xiàn)思路:
1、緩存所有的 Fragment
使用一個 List 將數(shù)據(jù)源對應的 Fragment 都緩存起來
2、更新數(shù)據(jù)源,刷新 Fragment
當有數(shù)據(jù)源更新的時候,從 List 中取出相應的 Fragment,然后刷新 Adapter
3、刪除數(shù)據(jù)時,刪除 List 中對應的 Fragment
當數(shù)據(jù)源中刪除某項時,將 List 中對應的 Fragment 也刪除,然后刷新 Adapter
小結
關于 ViewPager 數(shù)據(jù)源刷新比較麻煩的地方是從數(shù)據(jù)源中刪除數(shù)據(jù)的情況,這和 ViewPager 的實現(xiàn)方式有關,我們在解決該問題的時候要分具體情況來采取不同的方案。
上面提供的方案也不是完美的,還有很多不足,如果你在應用的過程中遇到了問題,那么請反饋給我,大家一起完善。
這里主要是探討關于 ViewPager 數(shù)據(jù)源刷新的問題,關于 ViewPager 的詳細使用不是本文重點,這里就不涉及了。
總結
以上是生活随笔為你收集整理的ViewPager刷新问题详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: RecyclerView ItemTou
- 下一篇: RecyclerView.Adapter