簡書 編程之樂
轉(zhuǎn)載請注明原創(chuàng)出處捕捂,謝謝交掏!
前言
FragmentPagerAdapter和FragmentStatePagerAdapter是我們開發(fā)中經(jīng)常遇到的兩個類狱从,尤其是和ViewPager的配合铐殃。幾乎我們每個Android開發(fā)者都被Fragment和ViewPager剂府,PopupWindow渊迁,適配等等一堆神坑折磨著慰照,尤其是Fragment神坑無數(shù),這些都是天天在用的組件琉朽,Google為什么留給我們這么多坑毒租。也正因如此,為了不掉進(jìn)坑里箱叁,就需要我們不斷去填坑墅垮。
下面是通過閱讀FragmentPagerAdapter和FragmentStatePagerAdapter能夠?qū)W到的知識點(diǎn):
- ViewPager刷新問題
- 適配器模式
- 觀察者模式
區(qū)別一: 狀態(tài)保存
我們在使用ViewPager的時候,經(jīng)常使用下面幾種方式:
ViewPager viewPager = findViewById(R.id.viewPager);
// 方式一
viewPager.setAdapter(new PagerAdapter() {
private String mTitles[] ;
private List<View> mViewList;
@Override
public int getCount() {
return mViewList.size();
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
View view = mViewList.get(position);
container.addView(view);
return view;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
@Override
public CharSequence getPageTitle(int position) {
return mTitles[position];
}
});
// 方式二
viewPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {
@Override
public Fragment getItem(int position) {
return fragments.get(position);
}
@Override
public int getCount() {
return fragments.size();
}
});
// 方式三
viewPager.setAdapter(new FragmentStatePagerAdapter(getSupportFragmentManager()) {
@Override
public Fragment getItem(int position) {
return fragments.get(position);
}
@Override
public int getCount() {
return fragments.size();
}
});
用法大家都比較熟悉了耕漱,其中FragmentPagerAdapter 和 FragmentStatePagerAdapter有什么區(qū)別呢算色?
根據(jù)兩個類的名稱就可以知道FragmentStatePagerAdapter似乎是保存狀態(tài)的,我們分別去這兩個類找下它們的區(qū)別螟够,發(fā)現(xiàn)它們都重寫了父類PageAdapter的方法:
public abstract class PagerAdapter {
// 省略
public static final int POSITION_UNCHANGED = -1;
public static final int POSITION_NONE = -2;
public Parcelable saveState() {
return null;
}
public void restoreState(Parcelable state, ClassLoader loader) {
}
}
分別查看它們的實現(xiàn):
FragmentPagerAdapter的實現(xiàn)
@Override
public Parcelable saveState() {
return null;
}
@Override
public void restoreState(Parcelable state, ClassLoader loader) {
}
FragmentStatePagerAdapter的實現(xiàn)
public Parcelable saveState() {
Bundle state = null;
if (mSavedState.size() > 0) {
state = new Bundle();
Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
mSavedState.toArray(fss);
state.putParcelableArray("states", fss);
}
for (int i=0; i<mFragments.size(); i++) {
Fragment f = mFragments.get(i);
if (f != null && f.isAdded()) {
if (state == null) {
state = new Bundle();
}
String key = "f" + i;
mFragmentManager.putFragment(state, key, f);
}
}
return state;
}
@Override
public void restoreState(Parcelable state, ClassLoader loader) {
if (state != null) {
Bundle bundle = (Bundle)state;
bundle.setClassLoader(loader);
Parcelable[] fss = bundle.getParcelableArray("states");
mSavedState.clear();
mFragments.clear();
if (fss != null) {
for (int i=0; i<fss.length; i++) {
mSavedState.add((Fragment.SavedState)fss[i]);
}
}
Iterable<String> keys = bundle.keySet();
for (String key: keys) {
if (key.startsWith("f")) {
int index = Integer.parseInt(key.substring(1));
Fragment f = mFragmentManager.getFragment(bundle, key);
if (f != null) {
while (mFragments.size() <= index) {
mFragments.add(null);
}
f.setMenuVisibility(false);
mFragments.set(index, f);
} else {
Log.w(TAG, "Bad fragment at key " + key);
}
}
}
}
}
可以很容易看出只有FragmentStatePagerAdapter對Fragment的狀態(tài)進(jìn)行了保存灾梦,而FragmentPagerAdapter則是空實現(xiàn)。
雖然兩個Adapter均有保存狀態(tài)的代碼妓笙,但是它們具體是在哪里被調(diào)用的呢若河?根據(jù)我們學(xué)過的Activity和Fragment的保存狀態(tài)的方式,我們知道狀態(tài)的恢復(fù)和保存一般在這些組件或者View里给郊,的確牡肉,它們是在ViewPager中。
public class ViewPager extends ViewGroup {
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.position = mCurItem;
if (mAdapter != null) {
ss.adapterState = mAdapter.saveState();
}
return ss;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
if (mAdapter != null) {
mAdapter.restoreState(ss.adapterState, ss.loader);
setCurrentItemInternal(ss.position, false, true);
} else {
mRestoredCurItem = ss.position;
mRestoredAdapterState = ss.adapterState;
mRestoredClassLoader = ss.loader;
}
}
}
因為ViewPager持有Adapter實例淆九,所以ViewPager的onSaveInstanceState和onRestoreInstanceState方法都是間接調(diào)用Adapter來執(zhí)行狀態(tài)的恢復(fù)和保存的统锤,我們看到ViewPager中間接調(diào)用了mAdapter.saveState()
和mAdapter.restoreState
毛俏。
區(qū)別二: 實例銷毀 vs 視圖銷毀
除了上面的區(qū)別外,F(xiàn)ragmentStatePagerAdapter和FragmentPagerAdapter唯一的區(qū)別就是對Fragment對象的處理了饲窿。
我們平常使用ViewPager + PageAdater時候需要重寫很多方法煌寇,如開頭的那幾個案例,而ViewPager + FragmentPagerAdapter(FragmentStatePagerAdapter) 僅僅實現(xiàn)getItem和getCount兩個方法就夠了逾雄,核心方法instantiateItem和destroyItem內(nèi)部已經(jīng)做好了實現(xiàn)阀溶。
先看FragmentStatePagerAdapter類
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
public Object instantiateItem(ViewGroup container, int position) {
if (mFragments.size() > position) {
Fragment f = mFragments.get(position);
if (f != null) {
return f;
}
}
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
// 實例化fragment(交給我們實現(xiàn)的getItem方法)
Fragment fragment = getItem(position);
if (mSavedState.size() > position) {
Fragment.SavedState fss = mSavedState.get(position);
if (fss != null) {
fragment.setInitialSavedState(fss);
}
}
// 如果緩存 <= ViewPager傳入的position,說明當(dāng)前位置還未存入緩存.
while (mFragments.size() <= position) {
// 先占個坑
mFragments.add(null);
}
fragment.setUserVisibleHint(false);
// 填坑
mFragments.set(position, fragment);
// 填充視圖
mCurTransaction.add(container.getId(), fragment);
return fragment;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment) object;
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
// 從緩存中移除
mFragments.set(position, null);
// 從FragmentManager中移除
mCurTransaction.remove(fragment);
}
再來看下FragmentPagerAdapter的兩個實現(xiàn)方法:
@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) {
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
}
return fragment;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
mCurTransaction.detach((Fragment)object);
}
FragmentStatePagerAdapter 內(nèi)部還做了個小緩存,這個不是重點(diǎn)鸦泳,我們主要關(guān)注
FragmentStatePagerAdapter
mCurTransaction.add(container.getId(), fragment);
mCurTransaction.remove(fragment);
和FragmentPagerAdapter
mCurTransaction.attach(fragment);
mCurTransaction.add(container.getId(), fragment,makeFragmentName(container.getId(), itemId));
mCurTransaction.detach((Fragment)object);
很明顯银锻,F(xiàn)ragmentStatePagerAdapter 對fragment進(jìn)行完全的添加和刪除操作,而FragmentPagerAdapter 則是對視圖進(jìn)行attach和detach做鹰。
總結(jié):
FragmentStatePagerAdapter 適合大量頁面击纬,不斷重建和銷毀
FragmentPagerAdapter 適合少量頁面,常駐內(nèi)存钾麸。
適配器模式
因為Android這個知識點(diǎn)有兩個設(shè)計模式的案例實在太經(jīng)典了更振,所以想順便拿來講一下,理解了這些饭尝,開發(fā)過程中常見兩個坑的問題:
- 懶加載
- 數(shù)據(jù)不更新
經(jīng)過查看源碼就非常容易解決了肯腕!
public class ViewPager {
private PagerAdapter mAdapter;
public void setAdapter(PagerAdapter adapter) {
adapter.xx();
adapter.xxx();
this.mAdapte = adapter;
// ....
requestLayout();
}
public void dataSetChanged() {
final int adapterCount = mAdapter.getCount();
// ....
mAdapter.destroyItem(this, ii.position, ii.object);
// ....
// ....
}
}
可以看到ViewPager持有的是PagerAdapter,ViewPager中間接調(diào)用了很多PagerAdapter的方法钥平,使用組合方式來代替繼承方式解耦实撒。
怎么看著那么像模板方法模式呢,設(shè)計模式中很多模式確實太像了帖池,比如代理模式 和 裝飾器模式奈惑。
組合優(yōu)于繼承,總之睡汹,能用組合實現(xiàn)的不要用繼承。
前不久我看一個開源項目的代碼寂殉,大量的繼承和模板方法模式囚巴,看的我真的快懷疑自己智商了。
偽觀察者模式
再來看下ViewPager的代碼:
public class ViewPager {
// 觀察者
private PagerObserver mObserver;
private PagerAdapter mAdapter;
public void setAdapter(PagerAdapter adapter) {
adapter.xx();
adapter.xxx();
this.mAdapte = adapter;
if (mAdapter != null) {
if (mObserver == null) {
// 實例化觀察者對象
mObserver = new PagerObserver();
}
// 傳遞一個觀察者mObserver對象供adapter調(diào)用
mAdapter.setViewPagerObserver(mObserver);
}
// ....
requestLayout();
}
public void dataSetChanged() {
final int adapterCount = mAdapter.getCount();
// ....
mAdapter.destroyItem(this, ii.position, ii.object);
// ....
// ....
}
/**
* 觀察者對象
*/
private class PagerObserver extends DataSetObserver {
PagerObserver() {
}
@Override
public void onChanged() {
dataSetChanged();
}
@Override
public void onInvalidated() {
dataSetChanged();
}
}
}
在setAdapter中mAdapter.setViewPagerObserver(mObserver);
這里傳遞了一個內(nèi)部類對象名稱叫 PagerObserver友扰,大家要注意了彤叉,這個地方雖然起名叫做觀察者,我認(rèn)為是不合理的村怪,確實Android提供給我們一個注冊觀察者的接口來監(jiān)聽(后面詳細(xì)講)秽浇,不過我們常常用notifyDataChange() 來通知ViewPager數(shù)據(jù)更新這里的默認(rèn)實現(xiàn) 并沒有真正用 觀察者模式,可能是Google偷懶了吧甚负。
mAdapter.setViewPagerObserver(mObserver)傳遞的這個對象 更像回調(diào)柬焕∩蟛校回調(diào)接口的本質(zhì)不就是傳遞一個對象嗎?斑举? C語言的實現(xiàn)則是傳遞指針搅轿。JavaScript傳遞function。
看下我們經(jīng)常調(diào)用的notifyDataSetChanged方法:
public abstract class PagerAdapter {
// 被觀察者,暫時不用管
private final DataSetObservable mObservable = new DataSetObservable();
// 冒充者,雖然也叫觀察者對象,但實際算是個回調(diào)對象
private DataSetObserver mViewPagerObserver;
void setViewPagerObserver(DataSetObserver observer) {
synchronized (this) {
mViewPagerObserver = observer;
}
}
public void notifyDataSetChanged() {
synchronized (this) {
if (mViewPagerObserver != null) {
mViewPagerObserver.onChanged();
}
}
mObservable.notifyChanged();
}
}
注釋寫的很明白富玷,PagerAdapter里面怎么可能同時充當(dāng)觀察者和被觀察者嘛璧坟,notifyDataSetChanged沒有用觀察者模式實現(xiàn)。
但是我們注意到了赎懦,notifyDataSetChanged方法的最后調(diào)用了
mObservable.notifyChanged();
這里才是真正的觀察者模式雀鹃,被觀察者準(zhǔn)備調(diào)用自己的方法通知所有的觀察者 數(shù)據(jù)改變了±剑可惜的是當(dāng)前 目前還木有人注冊褐澎,孤芳自賞!
這里暫時做個標(biāo)記伐蒋,我們最后在看Android的觀察者模式設(shè)計工三。
繼續(xù)跟蹤代碼,notifyDataSetChanged調(diào)用了mViewPagerObserver
這個偽娘的onChanged方法(ViewPager中),
onChanged()調(diào)用了ViewPager的dataSetChanged方法:
void dataSetChanged() {
// This method only gets called if our observer is attached, so mAdapter is non-null.
final int adapterCount = mAdapter.getCount();
mExpectedAdapterCount = adapterCount;
boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1
&& mItems.size() < adapterCount;
// newCurrItem 用于跟蹤標(biāo)記當(dāng)前ViewPager的所在頁
int newCurrItem = mCurItem;
boolean isUpdating = false;
// 遍歷ViewPager中所有的items(每個ItemInfo中包含著fragment實例,position等信息)
for (int i = 0; i < mItems.size(); i++) {
final ItemInfo ii = mItems.get(i);
// getItemPosition方法是我們根據(jù)需要重寫的方法,有三種值: POSITION_UNCHANGED和POSITION_NONE和pos(int類型)
final int newPos = mAdapter.getItemPosition(ii.object);
// (1). 如果getItemPosition()返回值是POSITION_UNCHANGED(默認(rèn)實現(xiàn)),不做處理
if (newPos == PagerAdapter.POSITION_UNCHANGED) {
continue;
}
// (2). 如果getItemPosition()返回值是POSITION_NONE,移除ViewPager的mItems中當(dāng)前正在遍歷著的ItemInfo
if (newPos == PagerAdapter.POSITION_NONE) {
mItems.remove(i);
i--;
if (!isUpdating) {
// 方法內(nèi)沒什么實際意義
mAdapter.startUpdate(this);
isUpdating = true;
}
// 同時調(diào)用adapter的銷毀方法銷毀當(dāng)前遍歷著的ItemInfo
mAdapter.destroyItem(this, ii.position, ii.object);
needPopulate = true;
continue;
}
// (3). 如果getItemPosition()返回值是其他的值(如newPos = 3),則相當(dāng)于把[首次初始化的ViewPager中ItemInfo的position]重新賦值為指定的值.換個位置,這個特性一般我們很少用到.
if (ii.position != newPos) {
if (ii.position == mCurItem) {
// 如果當(dāng)前for循環(huán)中遍歷的ItemInfo.position正好等于ViewPager中的當(dāng)前頁下標(biāo),跟蹤標(biāo)記
newCurrItem = newPos;
}
ii.position = newPos;
needPopulate = true;
}
}
if (isUpdating) {
mAdapter.finishUpdate(this);
}
Collections.sort(mItems, COMPARATOR);
// 根據(jù)前面的分析, (2) 和 (3)都會導(dǎo)致重新請求布局
if (needPopulate) {
// Reset our known page widths; populate will recompute them.
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.isDecor) {
lp.widthFactor = 0.f;
}
}
// 設(shè)置當(dāng)前頁,并重新布局或者是滾動到此頁
setCurrentItemInternal(newCurrItem, false, true);
requestLayout();
}
}
這個方法邏輯稍有點(diǎn)多先鱼,分析都寫在注釋里了俭正。
根據(jù)下面這段代碼
boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1
&& mItems.size() < adapterCount;
我們知道給ViewPager新添加View或Fragment沒有任何問題.它會自動處理,但是更新 就有問題了焙畔,我們可能希望把某個頁面替換掉掸读,比如A->B.
但是根據(jù)這段代碼的邏輯,不重寫getItemPosition方法(默認(rèn)POSITION_UNCHANGED)的話是不會有任何變化的宏多。
通過重寫getItemPosition()方法
final int newPos = mAdapter.getItemPosition(ii.object);
// ...
mAdapter.destroyItem(this, ii.position, ii.object);
我們可以看到ViewPager中只要有返回POSITION_NONE的項儿惫,那么就會銷毀該項并刷新。
但是不建議大家直接在adapter中這么干(雖然我是這么干的伸但,懶人):
反例如下:
@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);
}
這樣會導(dǎo)致調(diào)用notifyDataChange時候ViewPager中每個Fragment都會被 mAdapter.destroyItem肾请。我們只是想更新某個Item就夠了,這一下子全部都
destroyItem一遍更胖,性能肯定造成浪費(fèi)铛铁。
大家可以根據(jù)自己的邏輯修改進(jìn)行實現(xiàn),其中object就是Fragment對象或view對象却妨,比如設(shè)置tag之類的饵逐,只令某一項返回POSITION_NONE。
public int getItemPosition(Object object) {
return POSITION_NONE;
}
觀察者模式
首先聲明:雖然在ViewPager(充當(dāng)觀察者)和PagerAdapter(充當(dāng)被觀察者)中出現(xiàn)了觀察者模式的代碼彪标,但是ViewPager中并未注冊觀察者倍权。不過這里的案例非常經(jīng)典,不由得分析下作為記錄捞烟。同樣的薄声,ListView(充當(dāng)觀察者)和BaseAdapter(充當(dāng)被觀察者)則使用了這個模式并在ListView中注冊了觀察者当船,有興趣的可以查看相關(guān)源碼。
前面講了一個偽觀察者模式奸柬,繼續(xù)....
仍然是上次的代碼生年,notifyDataSetChanged最后一行調(diào)用了
mObservable.notifyChanged() 這才是正宗的觀察者模式。
public abstract class PagerAdapter {
// 被觀察者
private final DataSetObservable mObservable = new DataSetObservable();
public void notifyDataSetChanged() {
synchronized (this) {
// 冒充者
if (mViewPagerObserver != null) {
mViewPagerObserver.onChanged();
}
}
// 正宗
mObservable.notifyChanged();
}
public void registerDataSetObserver(DataSetObserver observer) {
mObservable.registerObserver(observer);
}
public void unregisterDataSetObserver(DataSetObserver observer) {
mObservable.unregisterObserver(observer);
}
}
如何在PagerAdapter 中給 被觀察者DataSetObservable 注冊一個觀察者?
注意:這段代碼僅做參考廓奕,Android并未真正注冊抱婉。
PagerAdapter pagerAdapter = new .. ;
pagerAdapter.registerDataSetObserver(new DataSetObserver() {
@Override
public void onChanged() {
super.onChanged();
// .... 實現(xiàn),這里copy偽娘那里的onChange()方法.
}
@Override
public void onInvalidated() {
super.onInvalidated();
// .... 實現(xiàn)
}
});
下面我們就來一起看看Android的被觀察者和觀察者是怎么寫的桌粉,可以借鑒參考下:
被觀察者
DataSetObservable
public class DataSetObservable extends Observable<DataSetObserver> {
public void notifyChanged() {
synchronized(mObservers) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
}
public void notifyInvalidated() {
synchronized (mObservers) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onInvalidated();
}
}
}
}
Observable
package android.database;
import java.util.ArrayList;
public abstract class Observable<T> {
protected final ArrayList<T> mObservers = new ArrayList<T>();
public void registerObserver(T observer) {
if (observer == null) {
throw new IllegalArgumentException("The observer is null.");
}
synchronized(mObservers) {
if (mObservers.contains(observer)) {
throw new IllegalStateException("Observer " + observer + " is already registered.");
}
mObservers.add(observer);
}
}
public void unregisterObserver(T observer) {
if (observer == null) {
throw new IllegalArgumentException("The observer is null.");
}
synchronized(mObservers) {
int index = mObservers.indexOf(observer);
if (index == -1) {
throw new IllegalStateException("Observer " + observer + " was not registered.");
}
mObservers.remove(index);
}
}
public void unregisterAll() {
synchronized(mObservers) {
mObservers.clear();
}
}
}
觀察者
package android.database;
public abstract class DataSetObserver {
public void onChanged() {
// Do nothing
}
public void onInvalidated() {
// Do nothing
}
}
這就是Android經(jīng)典的觀察者模式.
源碼擴(kuò)展
ListView和ViewPager在適配器模式和觀察者模式存在諸多相似蒸绩,舉一反三讓我們的理解更加透徹。
ListView在setAdapter() 中注冊的觀察者.
public void setAdapter(ListAdapter adapter) {
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
resetList();
mRecycler.clear();
if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
mAdapter = wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, adapter);
} else {
mAdapter = adapter;
}
mOldSelectedPosition = INVALID_POSITION;
mOldSelectedRowId = INVALID_ROW_ID;
super.setAdapter(adapter);
if (mAdapter != null) {
// 注冊觀察者
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);
}
requestLayout();
}