
作者:李旺成
時(shí)間:2016年5月3日
一、PagerAdapter介紹
先看效果圖

PagerAdapter簡(jiǎn)介
ListView 大家應(yīng)該都很熟悉吧!ListView 一般都需要一個(gè) Adapter 來(lái)填充數(shù)據(jù),如 ArrayAdapter、SimpleAdapter。PagerAdapter 就是 ViewPager 的 Adapter,與 ListView 的 Adapter 作用一樣。
ViewPager->PageAdapter == ListView->BaseAdapter
先看下官方介紹
官方介紹

PageAdapter 繼承自 Object,繼承結(jié)構(gòu)參考意義不大,那老實(shí)看文檔。文檔上沒(méi)有提供示例代碼,只是說(shuō)了下要自定義 PageAdapter 需要實(shí)現(xiàn)下面四個(gè)方法:
-
instantiateItem(ViewGroup container, int position):該方法的功能是創(chuàng)建指定位置的頁(yè)面視圖。適配器有責(zé)任增加即將創(chuàng)建的 View 視圖到這里給定的 container 中,這是為了確保在 finishUpdate(viewGroup) 返回時(shí) this is be done!
返回值:返回一個(gè)代表新增視圖頁(yè)面的 Object(Key),這里沒(méi)必要非要返回視圖本身,也可以這個(gè)頁(yè)面的其它容器。其實(shí)我的理解是可以代表當(dāng)前頁(yè)面的任意值,只要你可以與你增加的 View 一一對(duì)應(yīng)即可,比如 position 變量也可以做為 Key - destroyItem(ViewGroup container, int position, Object object):該方法的功能是移除一個(gè)給定位置的頁(yè)面。適配器有責(zé)任從容器中刪除這個(gè)視圖,這是為了確保在 finishUpdate(viewGroup) 返回時(shí)視圖能夠被移除
- getCount():返回當(dāng)前有效視圖的數(shù)量
-
isViewFromObject(View view, Object object):該函數(shù)用來(lái)判斷 instantiateItem() 函數(shù)所返回來(lái)的 Key 與一個(gè)頁(yè)面視圖是否是代表的同一個(gè)視圖(即它倆是否是對(duì)應(yīng)的,對(duì)應(yīng)的表示同一個(gè) View)
返回值:如果對(duì)應(yīng)的是同一個(gè)View,返回 true,否則返回 false
上面對(duì) PageAdapter 的四個(gè)抽象方法做了簡(jiǎn)要說(shuō)明,下面看看如何使用
簡(jiǎn)單使用
mContentVP.setAdapter(new PagerAdapter() {
@Override
public int getCount() {
return dataList.size();
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public 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;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
});
可以看到實(shí)現(xiàn) PagerAdapter 與 BaseAdapter 很類似,只是 PagerAdapter 的 isViewFromObject() 與 instantiateItem() 方法需要好好理解下。這里為了簡(jiǎn)化 PagerAdapter 的使用,我做了個(gè)簡(jiǎn)單的封裝:
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());
}
@Override
public int getCount() {
if (mDataList == null) return 0;
return mDataList.size();
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public 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;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView(mViewSparseArray.get(position));
}
public abstract View getView(int position);
}
APagerAdapter 類模仿 ListView 的 BaseAdapter,抽象出一個(gè) getView() 方法,在內(nèi)部使用 SparesArray 緩存所有顯示過(guò)的 View。這樣使用就很簡(jiǎn)單了,繼承 APagerAdapter,實(shí)現(xiàn) getView() 方法即可(可以參考:DemoPagerAdapter.java)。
PagerAdapter 刷新的問(wèn)題
提出問(wèn)題
在使用 ListView 的時(shí)候,我們往往習(xí)慣了更新 Adapter 的數(shù)據(jù)源,然后調(diào)用 Adapter 的 notifyDataSetChanged() 方法來(lái)刷新列表(有沒(méi)有點(diǎn) MVC 的感覺(jué))。
PagerAdapter 也有 notifyDataSetChanged() 方法,那我們按照這個(gè)流程來(lái)試試,看有沒(méi)有什么問(wèn)題。(ListView 的示例就不在這里演示了,感興趣的可以自己去試試,非常簡(jiǎn)單)
那么我的問(wèn)題是:“ViewPager 的 PagerAdapter 在數(shù)據(jù)源更新后,能否自動(dòng)刷新視圖?”
帶著問(wèn)題,我們做一些實(shí)驗(yàn),下面實(shí)驗(yàn)的思路是:修改數(shù)據(jù)源,然后通知 PagerAdapter 更新,查看視圖的變化。
實(shí)驗(yàn)環(huán)境準(zhǔn)備
看看實(shí)驗(yà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");
// 很簡(jiǎn)單的一個(gè) PagerAdapter
this.mContentVP.setAdapter(mPagerAdapter = new PagerAdapter() {
@Override
public int getCount() {
return mDataList.size();
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public 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;
}
@Override
public 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>
很簡(jiǎn)單的代碼,并且加了注釋,直接往下看實(shí)驗(yàn)。
PagerAdapter 刷新實(shí)驗(yàn)
1、更新數(shù)據(jù)源中的某項(xiàng)

對(duì)應(yīng)代碼:
private void refresh() {
mDataList.set(0, "更新數(shù)據(jù)源測(cè)試");
mPagerAdapter.notifyDataSetChanged();
}
問(wèn)題描述:在演示動(dòng)畫中可以看到,更新數(shù)據(jù)源之后視圖并沒(méi)有立即刷新,多滑動(dòng)幾次再次回到更新的 Item 時(shí)才更新(這里先看問(wèn)題,下面會(huì)細(xì)說(shuō))。
2、往數(shù)據(jù)源中添加數(shù)據(jù)

對(duì)應(yīng)代碼:
private void add() {
mDataList.add("這是新添加的Item");
mPagerAdapter.notifyDataSetChanged();
}
問(wèn)題描述:沒(méi)什么問(wèn)題,數(shù)據(jù)源添加數(shù)據(jù)后通知 PagerAdapter 刷新,ViewPager 中就多了一個(gè) Item。
3、從數(shù)據(jù)源中刪除數(shù)據(jù)

private void delete() {
mDataList.remove(0);
mPagerAdapter.notifyDataSetChanged();
}
問(wèn)題描述:這個(gè)問(wèn)題就較多了,首先,如果是刪除當(dāng)前 Item,那么會(huì)看到?jīng)]有任何反應(yīng);其次,如果刪除的不是當(dāng)前 Item,會(huì)發(fā)現(xiàn)出現(xiàn)了數(shù)據(jù)錯(cuò)亂,并且后面有 Item 滑不過(guò)去,但是按住往后滑的時(shí)候可以看到后面的 Item。
4、將數(shù)據(jù)源清空

private void clean() {
mDataList.clear();
mPagerAdapter.notifyDataSetChanged();
}
問(wèn)題描述:從上面的動(dòng)圖可以看到,清空數(shù)據(jù)源之后,會(huì)殘留一個(gè) Item。
說(shuō)明:先不要計(jì)較上面所寫的 PagerAdapter 是否有問(wèn)題,這里只是想引出問(wèn)題來(lái),下面會(huì)針對(duì) PagerAdapter、FragmentPagerAdapter 以及 FragmentStatePagerAdapter 來(lái)分析問(wèn)題原因和給出解決方案。
二、PagerAdapter
從上面的實(shí)驗(yàn)可以看出 ViewPager 不同于 ListView,如果單純的調(diào)用 ViewPager.getAdapter().notifyDataSetChanged() 方法(即 PagerAdapter 的 notifyDataSetChanged()方法)頁(yè)面并沒(méi)有刷新。
PagerAdapter 用于 ViewPager 的 Item 為普通 View的情況,這個(gè)相對(duì)簡(jiǎn)單,所以最先介紹。
相信很多同學(xué)都搜過(guò)類似的問(wèn)題 —— “PagerAdapter 的 notifyDataSetChanged() 不刷新?”。有的說(shuō)這是 bug,有的則認(rèn)為 Google 是特意這樣設(shè)計(jì)的,個(gè)人傾向后一種觀點(diǎn)(我覺(jué)得這是 Google 為了 ViewPager 性能考慮而設(shè)計(jì)的,畢竟 ViewPager 需要顯示“很多大的”視圖,而且要防止用戶滑動(dòng)時(shí)覺(jué)得卡頓)。
ViewPager 刷新分析
先來(lái)了解下 ViewPager 的刷新過(guò)程:
1、刷新的起始
ViewPager 的刷新是從調(diào)用其 PagerAdapter 的 notifyDataSetChanged() 方法開始的,那先看看該方法的源碼(在源碼面前一切無(wú)所遁形...):
/**
* 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)了兩個(gè)關(guān)鍵的成員變量:
private final DataSetObservable mObservable = new DataSetObservable();
private DataSetObserver mViewPagerObserver;
觀察者模式,有沒(méi)有?先不著急分析這個(gè)是不是觀察者模式,來(lái)看看 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ā)生了的代碼。代碼很簡(jiǎn)單,那關(guān)鍵是這個(gè) mObservers 包含哪些 Observer 呢?
3、DataSetObserver
直接從 mObservers 點(diǎn)進(jìn)去你會(huì)發(fā)現(xiàn)這個(gè):
protected final ArrayList<T> mObservers = new ArrayList<T>();
-_-',這是個(gè)泛型,坑了!還好 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
}
}
一個(gè)抽象類,里面兩個(gè)空方法,這個(gè)好辦,找他的子類(AndroidStudio 中 將光標(biāo)放到類名上,按 F4):

總算找到你了,就是用紅線框出來(lái)的那條,雙擊,定位過(guò)去。
4、PagerObserver 內(nèi)部類
PagerObserver 是 ViewPager 中的一個(gè)內(nèi)部類,實(shí)現(xiàn)也很簡(jiǎn)單,就是調(diào)用了 ViewPager 中的 dataSetChanged() 方法,真正的關(guān)鍵來(lái)了。
private class PagerObserver extends DataSetObserver {
@Override
public void onChanged() {
dataSetChanged();
}
@Override
public void onInvalidated() {
dataSetChanged();
}
}
5、ViewPager 的 dataSetChanged()
這個(gè)方法的實(shí)現(xiàn)較長(zhǎng),里面的邏輯看上去挺復(fù)雜的,這里就不展示全部的源碼了,列下關(guān)鍵點(diǎ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)里面有兩個(gè) continue 語(yǔ)句,這可能是比較關(guān)鍵的代碼,幸好不用我們繼續(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 的位置如果沒(méi)有發(fā)生變化,則返回 POSITION_UNCHANGED。如果返回了 POSITION_NONE,表示該位置的 Item 已經(jīng)不存在了。默認(rèn)的實(shí)現(xiàn)是假設(shè) Item 的位置永遠(yuǎn)不會(huì)發(fā)生變化,而返回 POSITION_UNCHANGED。(參考自:追溯源碼解決android疑難有關(guān)問(wèn)題1-Viewpager之notifyDataSetChanged無(wú)刷新)
上面在源碼里面跟了一大圈是不是還是感覺(jué)沒(méi)有明朗,因?yàn)檫€有一個(gè)很關(guān)鍵的類 —— PagerAdapter 沒(méi)有介紹,再給點(diǎn)耐心,繼續(xù)。
6、PagerAdapter 的工作流程
其實(shí)就是 PagerAdapter 中方法的執(zhí)行順序,來(lái)看看 Leo8573 的分析(個(gè)人感覺(jué)基本說(shuō)到位了,所以直接拷過(guò)來(lái)了):
PagerAdapter 作為 ViewPager 的適配器,無(wú)論 ViewPager 有多少頁(yè),PagerAdapter 在初始化時(shí)也只初始化開始的2個(gè) View,即調(diào)用2次instantiateItem 方法。而接下來(lái)每當(dāng) ViewPager 滑動(dòng)時(shí),PagerAdapter 都會(huì)調(diào)用 destroyItem 方法將距離該頁(yè)2個(gè)步幅以上的那個(gè) View 銷毀,以此保證 PagerAdapter 最多只管轄3個(gè) View,且當(dāng)前 View 是3個(gè)中的中間一個(gè),如果當(dāng)前 View 缺少兩邊的 View,那么就 instantiateItem,如里有超過(guò)2個(gè)步幅的就 destroyItem。
簡(jiǎn)易圖示:
*
------+---+---+---+------
... 0 | 1 | 2 | 3 | 4 ...
------+---+---+---+------
當(dāng)前 View 為2號(hào) View,所以 PagerAdapter 管轄1、2、3三個(gè) View,接下來(lái)向左滑動(dòng)-->
*
------+---+---+---+------
... 1 | 2 | 3 | 4 | 5 ...
------+---+---+---+------
滑動(dòng)后,當(dāng)前 View 變?yōu)?號(hào) View,PagerAdapter 會(huì) destroyItem 0號(hào)View,instantiateItem 5號(hào) View,所以 PagerAdapter 管轄2、3、4三個(gè) View。(參考自:關(guān)于ViewPager的數(shù)據(jù)更新問(wèn)題小結(jié))
總結(jié)一下: Viewpager 的刷新過(guò)程是這樣的,在每次調(diào)用 PagerAdapter 的 notifyDataSetChanged() 方法時(shí),都會(huì)激活 getItemPosition(Object object) 方法,該方法會(huì)遍歷 ViewPager 的所有 Item(由緩存的 Item 數(shù)量決定,默認(rèn)為當(dāng)前頁(yè)和其左右加起來(lái)共3頁(yè),這個(gè)可以自行設(shè)定,但是至少會(huì)緩存2頁(yè)),為每個(gè) Item 返回一個(gè)狀態(tài)值(POSITION_NONE/POSITION_UNCHANGED),如果是 POSITION_NONE,那么該 Item 會(huì)被 destroyItem(ViewGroup container, int position, Object object) 方法 remove 掉,然后重新加載,如果是 POSITION_UNCHANGED,就不會(huì)重新加載,默認(rèn)是 POSITION_UNCHANGED,所以如果不重寫 getItemPosition(Object object),修改返回值,就無(wú)法看到 notifyDataSetChanged() 的刷新效果。
最簡(jiǎn)單的解決方案
那就是直接一刀切:重寫 PagerAdapter 的 getItemPosition(Object object) 方法,將返回值固定為 POSITION_NONE。
先看看效果:
單解決方案示例“)
上代碼(PagerAdapterActivity.java):
@Override
public int getItemPosition(Object object) {
// 最簡(jiǎn)單解決 notifyDataSetChanged() 頁(yè)面不刷新問(wèn)題的方法
return POSITION_NONE;
}
該方案的缺點(diǎn):有個(gè)很明顯的缺陷,那就是會(huì)刷新所有的 Item,這將導(dǎo)致系統(tǒng)資源的浪費(fèi),所以這種方式不適合數(shù)據(jù)量較大的場(chǎng)景。
注意:
這種方式還有一個(gè)需要注意的地方,就是重寫 destoryItem() 方法:
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
// 把 Object 強(qiáng)轉(zhuǎn)為 View,然后將 view 從 ViewGroup 中清除
container.removeView((View) object);
}
最簡(jiǎn)方案的優(yōu)化
這里提供一個(gè)思路,畢竟場(chǎng)景太多,相信大家理解了思路要實(shí)現(xiàn)就很簡(jiǎn)單了,閑話不多說(shuō)。
思路:在 instantiateItem() 方法中給每個(gè) View 添加 tag(使用 setTag() 方法),然后在 getItemPosition() 方法中通過(guò) View.getTag() 來(lái)判斷是否是需要刷新的頁(yè)面,是就返回 POSITION_NONE,否就返回 POSITION_UNCHANGED。 (參考自:ViewPager刷新單個(gè)頁(yè)面的方法)
注意:這里有一點(diǎn)要注意的是,當(dāng)清空數(shù)據(jù)源的時(shí)候需要返回 POSITION_NONE,可用如下代碼:
if (mDataList != null && mDataList.size()==0) {
return POSITION_NONE;
}
關(guān)于 PagerAdapter 的介紹就到這里了,雖然 FragmentPagerAdapter 與 FragmentStatePagerAdapter 都是繼承自 PagerAdapter。但是,這兩個(gè)是專門為以 Fragment 為 Item 的 ViewPager 所準(zhǔn)備的,所以有其特殊性。且看下面的介紹。
三、FragmentPagerAdapter
簡(jiǎn)介
上面通過(guò)使 getItemPosition() 方法返回 POSITION_NONE 到達(dá)數(shù)據(jù)源變化(也就是調(diào)用 notifyDataSetChanged())時(shí),刷新視圖的目的。但是當(dāng)我們使用 Fragment 作為 ViewPager 的 Item 時(shí),就需要多考慮一些了,而且一般是使用 FragmentPagerAdapter 或者 FragmentStatePagerAdapter。
這里不展開討論 FragmentPagerAdapter 與 FragmentStatePagerAdapter 的異同和使用場(chǎng)景了,感興趣的可以看看這篇文章:FragmentPagerAdapter與FragmentStatePagerAdapter區(qū)別。
下面先來(lái)看看使用 FragmentPagerAdapter 時(shí),如何在數(shù)據(jù)源發(fā)生變化時(shí),刷新 Fragment 或者動(dòng)態(tài)改變 Items 的數(shù)量。
方案:清除 FragmentManager 中緩存的 Fragment
先看效果:

實(shí)現(xiàn)上圖效果的關(guā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();
}
@Override
public int getCount() {
return this.mFragmentList.size();
}
public int getItemPosition(Object object) {
return POSITION_NONE;
}
@Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
}
3、思路分析
上面的代碼思路很簡(jiǎn)單,就是當(dāng)數(shù)據(jù)源發(fā)生變化時(shí),先將 FragmentManger 里面所有緩存的 Fragment 全部清除,然后重新創(chuàng)建,這樣達(dá)到刷新視圖的目的。
但是,這樣做有一個(gè)缺點(diǎn),那就是會(huì)造成不必要的浪費(fèi),會(huì)影響性能。還有就是必須使用一個(gè) List 緩存所有的 Fragment,這也得占用不少內(nèi)存...
思路挺簡(jiǎn)單,這里不再贅述,那看看有沒(méi)有什么可以優(yōu)化的。
優(yōu)化:通過(guò) Tag 獲取緩存的 Fragment
先看效果:

從上面的動(dòng)圖上可以看到,更新某一個(gè) Fragment 沒(méi)有問(wèn)題,清空數(shù)據(jù)源的時(shí)候也沒(méi)有,添加當(dāng)然也沒(méi)什么問(wèn)題;請(qǐng)注意刪除的效果,雖然,目的 Fragment 確實(shí)從 ViewPager 中移除了,但是滑動(dòng)后面的頁(yè)面會(huì)發(fā)現(xiàn)出現(xiàn)了數(shù)據(jù)錯(cuò)亂。
分析一下優(yōu)化的思路:
先來(lái)了解 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 時(shí)調(diào)用了 findFragmentByTag() 方法,而這個(gè) Tag 是由 makeFragmentName() 方法生成的。繼續(xù)往下可以看到每一個(gè) Fragment 都打上了一個(gè)標(biāo)簽(在 mCurTransaction.add() 方法中)。
也就是說(shuō)是 FragmentManager 通過(guò) Tag 找相應(yīng)的 Fragment,從而達(dá)到緩存 Fragment 的目的。如果可以找到,就不會(huì)創(chuàng)建新的 Fragment,F(xiàn)ragment 的 onCreate()、onCreateView() 等方法都不會(huì)再次調(diào)用。
那優(yōu)化的思路就有了:
首先,需要緩存所有 Fragment 的 Tag,代碼如下:
private List<String> mTagList; // 用來(lái)存放所有的 Tag
// 生成 Tag
// 直接從 FragmentPageAdapter 源碼里拷貝 Fragment 生成 Tag 的方法
private String makeFragmentName(int viewId, int index) {
return "android:switcher:" + viewId + ":" + index;
}
// 將 Tag 緩存到 List 中
@Override
public Object instantiateItem(ViewGroup container, int position) {
mTagList.add(position, makeFragmentName(container.getId(),
(int) getItemId(position)));
return super.instantiateItem(container, position);
}
其次,在更新 Fragment 時(shí),使用相應(yīng)的 Tag 去 FragmentManamager 中找相應(yīng)的 Fragment,如果存在,就直接更新,代碼如下:
public void update(int position, String str) {
Fragment fragment = mFragmentManager.findFragmentByTag(mTagList.get(position));
if (fragment == null) return;
if (fragment instanceof FragmentTest) {
((FragmentTest)fragment).update(str);
}
notifyDataSetChanged();
}
該方法需要自行在 Fragment 中提供。
最后,對(duì)于動(dòng)態(tài)改變 ViewPager 中 Fragment 的數(shù)量,如果是添加,那沒(méi)什么要注意的;但是刪除有點(diǎn)棘手。
在上面的動(dòng)態(tài)上看到,刪除一個(gè) Fragment 后會(huì)出現(xiàn)混亂,這里沒(méi)有進(jìn)一步去研究了,這里僅提供一個(gè)示例供參考(這個(gè)示例代碼有問(wè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();
}
注意:
這個(gè)”優(yōu)化“示例,僅僅適用于在只需要更新某個(gè) Fragment 的場(chǎng)景,關(guān)于動(dòng)態(tài)刪除 Fragment,該”優(yōu)化“方案并不適用,也不推薦使用。
四、FragmentStatePagerAdapter
先看效果:

簡(jiǎn)介
FragmentStatePagerAdapter 與 FragmentPagerAdapter 類似,這兩個(gè)類都繼承自 PagerAdapter。但是,和 FragmentPagerAdapter 不一樣的是,F(xiàn)ragmentStatePagerAdapter 只保留當(dāng)前頁(yè)面,當(dāng)頁(yè)面離開視線后,就會(huì)被消除,釋放其資源;而在頁(yè)面需要顯示時(shí),生成新的頁(yè)面(這和 ListView 的實(shí)現(xiàn)一樣)。這種方式的好處就是當(dāng)擁有大量的頁(yè)面時(shí),不必在內(nèi)存中占用大量的內(nèi)存。(參考自:FragmentPagerAdapter與FragmentStatePagerAdapter區(qū)別)
FragmentStatePagerAdapter 的實(shí)現(xiàn)與 FragmentPagerAdapter 有很大區(qū)別,如果照搬上述 FragmentPagerAdapter 刷新數(shù)據(jù)的方式,你會(huì)發(fā)現(xiàn)沒(méi)有什么問(wèn)題(可以使用 FPagerAdapter11.java 測(cè)試)。
另一種思路
但是,我在項(xiàng)目中實(shí)際應(yīng)用的時(shí)候(Fragment 比較復(fù)雜,里面有網(wǎng)絡(luò)任務(wù)等)出現(xiàn)了 IllegalStateException,發(fā)生在 ”fragmentTransaction.remove(f);“ 時(shí)。當(dāng)時(shí)找了一些文章沒(méi)有解決該問(wèn)題,考慮到項(xiàng)目中的 Fragment 里面邏輯過(guò)多,就換思路,沒(méi)有在這個(gè)上面繼續(xù)深究了。
如果,你也是這樣使用 FragmentStatePagerAdapter 來(lái)動(dòng)態(tài)改變 ViewPager 中 Fragment,并且在 remove Fragment 時(shí)遇到了 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();
}
@Override
public int getCount() {
return this.mFragmentList.size();
}
public int getItemPosition(Object object) {
return POSITION_NONE;
}
@Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
}
對(duì)應(yīng)的測(cè)試 Activity 見(jiàn) FSPagerAdapterActivity.java。
上面的代碼挺簡(jiǎn)單,稍微解釋一下實(shí)現(xiàn)思路:
1、緩存所有的 Fragment
使用一個(gè) List 將數(shù)據(jù)源對(duì)應(yīng)的 Fragment 都緩存起來(lái)
2、更新數(shù)據(jù)源,刷新 Fragment
當(dāng)有數(shù)據(jù)源更新的時(shí)候,從 List 中取出相應(yīng)的 Fragment,然后刷新 Adapter
3、刪除數(shù)據(jù)時(shí),刪除 List 中對(duì)應(yīng)的 Fragment
當(dāng)數(shù)據(jù)源中刪除某項(xiàng)時(shí),將 List 中對(duì)應(yīng)的 Fragment 也刪除,然后刷新 Adapter
小結(jié)
關(guān)于 ViewPager 數(shù)據(jù)源刷新比較麻煩的地方是從數(shù)據(jù)源中刪除數(shù)據(jù)的情況,這和 ViewPager 的實(shí)現(xiàn)方式有關(guān),我們?cè)诮鉀Q該問(wèn)題的時(shí)候要分具體情況來(lái)采取不同的方案。
上面提供的方案也不是完美的,還有很多不足,如果你在應(yīng)用的過(guò)程中遇到了問(wèn)題,那么請(qǐng)反饋給我,大家一起完善。
這里主要是探討關(guān)于 ViewPager 數(shù)據(jù)源刷新的問(wèn)題,關(guān)于 ViewPager 的詳細(xì)使用不是本文重點(diǎn),這里就不涉及了。
項(xiàng)目地址
參考
ViewPager 詳解(二)---詳解四大函數(shù)
pagerAdapter arrayList 數(shù)據(jù)清空,Item 不銷毀的bug解決
ViewPager刷新單個(gè)頁(yè)面的方法
ViewPager動(dòng)態(tài)加載、刪除頁(yè)面
ViewPager+Fragment滑動(dòng)界面,并做延遲加載【新版】
關(guān)于ViewPager的數(shù)據(jù)更新問(wèn)題小結(jié)
Viewpager+fragment數(shù)據(jù)更新問(wèn)題解析
追溯源碼解決android疑難有關(guān)問(wèn)題1-Viewpager之notifyDataSetChanged無(wú)刷新
解決fragment+viewpager第二次進(jìn)入的時(shí)候沒(méi)有數(shù)據(jù)的問(wèn)題
FragmentPagerAdapter刷新fragment最完美解決方案
Viewpager+fragment數(shù)據(jù)更新問(wèn)題解析
FragmentPagerAdapter與FragmentStatePagerAdapter區(qū)別