簡書 編程之樂
轉載請注明原創出處,謝謝!
前言
FragmentPagerAdapter和FragmentStatePagerAdapter是我們開發中經常遇到的兩個類,尤其是和ViewPager的配合。幾乎我們每個Android開發者都被Fragment和ViewPager,PopupWindow,適配等等一堆神坑折磨著,尤其是Fragment神坑無數,這些都是天天在用的組件,Google為什么留給我們這么多坑。也正因如此,為了不掉進坑里,就需要我們不斷去填坑。
下面是通過閱讀FragmentPagerAdapter和FragmentStatePagerAdapter能夠學到的知識點:
- ViewPager刷新問題
- 適配器模式
- 觀察者模式
區別一: 狀態保存
我們在使用ViewPager的時候,經常使用下面幾種方式:
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有什么區別呢?
根據兩個類的名稱就可以知道FragmentStatePagerAdapter似乎是保存狀態的,我們分別去這兩個類找下它們的區別,發現它們都重寫了父類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) {
}
}
分別查看它們的實現:
FragmentPagerAdapter的實現
@Override
public Parcelable saveState() {
return null;
}
@Override
public void restoreState(Parcelable state, ClassLoader loader) {
}
FragmentStatePagerAdapter的實現
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的狀態進行了保存,而FragmentPagerAdapter則是空實現。
雖然兩個Adapter均有保存狀態的代碼,但是它們具體是在哪里被調用的呢?根據我們學過的Activity和Fragment的保存狀態的方式,我們知道狀態的恢復和保存一般在這些組件或者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方法都是間接調用Adapter來執行狀態的恢復和保存的,我們看到ViewPager中間接調用了mAdapter.saveState()
和mAdapter.restoreState
。
區別二: 實例銷毀 vs 視圖銷毀
除了上面的區別外,FragmentStatePagerAdapter和FragmentPagerAdapter唯一的區別就是對Fragment對象的處理了。
我們平常使用ViewPager + PageAdater時候需要重寫很多方法,如開頭的那幾個案例,而ViewPager + FragmentPagerAdapter(FragmentStatePagerAdapter) 僅僅實現getItem和getCount兩個方法就夠了,核心方法instantiateItem和destroyItem內部已經做好了實現。
先看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(交給我們實現的getItem方法)
Fragment fragment = getItem(position);
if (mSavedState.size() > position) {
Fragment.SavedState fss = mSavedState.get(position);
if (fss != null) {
fragment.setInitialSavedState(fss);
}
}
// 如果緩存 <= ViewPager傳入的position,說明當前位置還未存入緩存.
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的兩個實現方法:
@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 內部還做了個小緩存,這個不是重點,我們主要關注
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);
很明顯,FragmentStatePagerAdapter 對fragment進行完全的添加和刪除操作,而FragmentPagerAdapter 則是對視圖進行attach和detach。
總結:
FragmentStatePagerAdapter 適合大量頁面,不斷重建和銷毀
FragmentPagerAdapter 適合少量頁面,常駐內存。
適配器模式
因為Android這個知識點有兩個設計模式的案例實在太經典了,所以想順便拿來講一下,理解了這些,開發過程中常見兩個坑的問題:
- 懶加載
- 數據不更新
經過查看源碼就非常容易解決了!
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中間接調用了很多PagerAdapter的方法,使用組合方式來代替繼承方式解耦。
怎么看著那么像模板方法模式呢,設計模式中很多模式確實太像了,比如代理模式 和 裝飾器模式。
組合優于繼承,總之,能用組合實現的不要用繼承。
前不久我看一個開源項目的代碼,大量的繼承和模板方法模式,看的我真的快懷疑自己智商了。
偽觀察者模式
再來看下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調用
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);
這里傳遞了一個內部類對象名稱叫 PagerObserver,大家要注意了,這個地方雖然起名叫做觀察者,我認為是不合理的,確實Android提供給我們一個注冊觀察者的接口來監聽(后面詳細講),不過我們常常用notifyDataChange() 來通知ViewPager數據更新這里的默認實現 并沒有真正用 觀察者模式,可能是Google偷懶了吧。
mAdapter.setViewPagerObserver(mObserver)傳遞的這個對象 更像回調。回調接口的本質不就是傳遞一個對象嗎?? C語言的實現則是傳遞指針。JavaScript傳遞function。
看下我們經常調用的notifyDataSetChanged方法:
public abstract class PagerAdapter {
// 被觀察者,暫時不用管
private final DataSetObservable mObservable = new DataSetObservable();
// 冒充者,雖然也叫觀察者對象,但實際算是個回調對象
private DataSetObserver mViewPagerObserver;
void setViewPagerObserver(DataSetObserver observer) {
synchronized (this) {
mViewPagerObserver = observer;
}
}
public void notifyDataSetChanged() {
synchronized (this) {
if (mViewPagerObserver != null) {
mViewPagerObserver.onChanged();
}
}
mObservable.notifyChanged();
}
}
注釋寫的很明白,PagerAdapter里面怎么可能同時充當觀察者和被觀察者嘛,notifyDataSetChanged沒有用觀察者模式實現。
但是我們注意到了,notifyDataSetChanged方法的最后調用了
mObservable.notifyChanged();
這里才是真正的觀察者模式,被觀察者準備調用自己的方法通知所有的觀察者 數據改變了。可惜的是當前 目前還木有人注冊,孤芳自賞!
這里暫時做個標記,我們最后在看Android的觀察者模式設計。
繼續跟蹤代碼,notifyDataSetChanged調用了mViewPagerObserver
這個偽娘的onChanged方法(ViewPager中),
onChanged()調用了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 用于跟蹤標記當前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方法是我們根據需要重寫的方法,有三種值: POSITION_UNCHANGED和POSITION_NONE和pos(int類型)
final int newPos = mAdapter.getItemPosition(ii.object);
// (1). 如果getItemPosition()返回值是POSITION_UNCHANGED(默認實現),不做處理
if (newPos == PagerAdapter.POSITION_UNCHANGED) {
continue;
}
// (2). 如果getItemPosition()返回值是POSITION_NONE,移除ViewPager的mItems中當前正在遍歷著的ItemInfo
if (newPos == PagerAdapter.POSITION_NONE) {
mItems.remove(i);
i--;
if (!isUpdating) {
// 方法內沒什么實際意義
mAdapter.startUpdate(this);
isUpdating = true;
}
// 同時調用adapter的銷毀方法銷毀當前遍歷著的ItemInfo
mAdapter.destroyItem(this, ii.position, ii.object);
needPopulate = true;
continue;
}
// (3). 如果getItemPosition()返回值是其他的值(如newPos = 3),則相當于把[首次初始化的ViewPager中ItemInfo的position]重新賦值為指定的值.換個位置,這個特性一般我們很少用到.
if (ii.position != newPos) {
if (ii.position == mCurItem) {
// 如果當前for循環中遍歷的ItemInfo.position正好等于ViewPager中的當前頁下標,跟蹤標記
newCurrItem = newPos;
}
ii.position = newPos;
needPopulate = true;
}
}
if (isUpdating) {
mAdapter.finishUpdate(this);
}
Collections.sort(mItems, COMPARATOR);
// 根據前面的分析, (2) 和 (3)都會導致重新請求布局
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;
}
}
// 設置當前頁,并重新布局或者是滾動到此頁
setCurrentItemInternal(newCurrItem, false, true);
requestLayout();
}
}
這個方法邏輯稍有點多,分析都寫在注釋里了。
根據下面這段代碼
boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1
&& mItems.size() < adapterCount;
我們知道給ViewPager新添加View或Fragment沒有任何問題.它會自動處理,但是更新 就有問題了,我們可能希望把某個頁面替換掉,比如A->B.
但是根據這段代碼的邏輯,不重寫getItemPosition方法(默認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);
}
這樣會導致調用notifyDataChange時候ViewPager中每個Fragment都會被 mAdapter.destroyItem。我們只是想更新某個Item就夠了,這一下子全部都
destroyItem一遍,性能肯定造成浪費。
大家可以根據自己的邏輯修改進行實現,其中object就是Fragment對象或view對象,比如設置tag之類的,只令某一項返回POSITION_NONE。
public int getItemPosition(Object object) {
return POSITION_NONE;
}
觀察者模式
首先聲明:雖然在ViewPager(充當觀察者)和PagerAdapter(充當被觀察者)中出現了觀察者模式的代碼,但是ViewPager中并未注冊觀察者。不過這里的案例非常經典,不由得分析下作為記錄。同樣的,ListView(充當觀察者)和BaseAdapter(充當被觀察者)則使用了這個模式并在ListView中注冊了觀察者,有興趣的可以查看相關源碼。
前面講了一個偽觀察者模式,繼續....
仍然是上次的代碼,notifyDataSetChanged最后一行調用了
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();
// .... 實現,這里copy偽娘那里的onChange()方法.
}
@Override
public void onInvalidated() {
super.onInvalidated();
// .... 實現
}
});
下面我們就來一起看看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經典的觀察者模式.
源碼擴展
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();
}