FragmentPagerAdapter和FragmentStatePagerAdapter是咱們開發中常常遇到的兩個類,尤爲是和ViewPager的配合。幾乎咱們每一個Android開發者都被Fragment和ViewPager,PopupWindow,適配等等一堆神坑折磨着,尤爲是Fragment神坑無數,這些都是每天在用的組件,Google爲何留給咱們這麼多坑。也正因如此,爲了避免掉進坑裏,就須要咱們不斷去填坑。
下面是經過閱讀FragmentPagerAdapter和FragmentStatePagerAdapter可以學到的知識點:java
咱們在使用ViewPager的時候,常用下面幾種方式:android
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) { } }
分別查看它們的實現:ide
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則是空實現。this
雖然兩個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
。代理
除了上面的區別外,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(); }