众所周知,FragmentStatePagerAdapter是谷歌官方专门为Fragment和ViewPager推出的Adapter,其特点是为Fragment提供缓存,避免重复加载Fragment。这个我们从源代码就可以看得出来:
@Override
public Object instantiateItem(ViewGroup container, int position) {
// If we already have this item instantiated, there is nothing
// to do. This can happen when we are restoring the entire pager
// from its saved state, where the fragment manager has already
// taken care of restoring the fragments we previously had instantiated.
if (mFragments.size() > position) {
// 存在缓存表示该Fragment已经加载过了
// 值得注意的是,这个地方Fragment的缓存是与position绑定在一起的
Fragment f = mFragments.get(position);
if (f != null) {
return f;
}
}
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
Fragment fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
if (mSavedState.size() > position) {
Fragment.SavedState fss = mSavedState.get(position);
if (fss != null) {
//Fragment未加载,但是存在缓存的状态信息
fragment.setInitialSavedState(fss);
}
}
while (mFragments.size() <= position) {
mFragments.add(null);
}
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
mFragments.set(position, fragment);
mCurTransaction.add(container.getId(), fragment);
return fragment;
}
这就是FragmentStatePagerAdapter加载缓存的代码,其中两个关键点,我都加了中文注释。在这里我要讲的是第一个地方,也就是Fragment是和position绑定在一起的,这样的话就会有一个问题,当我们想要隐藏或者显示(插入)其中某一个Fragment,我们就必须要让所有的Fragment重新走一遍生命周期。
这是因为,想要刷新ViewPager的Fragment必须修改getItemPosition(Object object)这个方法,使其返回POSITION_NONE,但是这样的话,所有的Fragment都会先被remove,然后再重新add。
/**
* Called when the host view is attempting to determine if an item's position
* has changed. Returns {@link #POSITION_UNCHANGED} if the position of the given
* item has not changed or {@link #POSITION_NONE} if the item is no longer present
* in the adapter.
*/
public int getItemPosition(Object object) {
return POSITION_UNCHANGED;
}
然儿这并不是我们想要,因此就有必要寻找一种替代方案。我们依然可以从getItemPosition这个方法入手,getItemPosition返回值表示的是page的位置,也就是说我们可以用这个方法来调整page的位置,而fragment也不需要重新加载。
既然知道问题在哪,只要对症下药就好了。修改一下FragmentStatePagerAdapter的部分逻辑即可,具体代码如下:
/**
* Description: Copied from android.support.v4.app.FragmentStatePagerAdapter, Fix bug for that fragments cache not refreshed when adapter items's position changed.
* FixBug#修复Fragment的位置更新而缓存未更新导致显示错误以及crash的问题
* Author: xuqingqi
* E-mail: [email protected]
* Date: 2017/12/8
*/
@Keep
public abstract class FragmentStatePagerAdapterCompat extends PagerAdapter {
private static final String TAG = FragmentStatePagerAdapterCompat.class.getSimpleName();
private static final boolean DEBUG = false;
private final FragmentManager mFragmentManager;
private FragmentTransaction mCurTransaction = null;
private SparseArray mSavedState = new SparseArray();
private SparseArray mFragments = new SparseArray();
private Fragment mCurrentPrimaryItem = null;
public FragmentStatePagerAdapterCompat(FragmentManager fm) {
mFragmentManager = fm;
}
/**
* Return the Fragment associated with a specified position.
*/
public abstract Fragment getItem(int position);
/**
* Return a unique identifier for the item at the given position.
*
* The default implementation returns the given position.
* Subclasses should override this method if the positions of items can change.
*
* @param position Position within this adapter
* @return Unique identifier for the item at position
*/
//添加一个方法,将Fragment和Id绑定在一起而不是position
public int getItemId(int position) {
return position;
}
@Override
public void startUpdate(ViewGroup container) {
if (container.getId() == View.NO_ID) {
throw new IllegalStateException("ViewPager with adapter " + this
+ " requires a view id");
}
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
// If we already have this item instantiated, there is nothing
// to do. This can happen when we are restoring the entire pager
// from its saved state, where the fragment manager has already
// taken care of restoring the fragments we previously had instantiated.
int itemId = getItemId(position);
Fragment f = mFragments.get(itemId);
if (f != null) {
return f;
}
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
Fragment fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
Fragment.SavedState fss = mSavedState.get(itemId);
if (fss != null) {
fragment.setInitialSavedState(fss);
}
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
mFragments.put(itemId, 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();
}
if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
+ " v=" + ((Fragment)object).getView());
int index = mFragments.indexOfValue(fragment);
if (index >= 0) {
int itemId = mFragments.keyAt(index);
mSavedState.put(itemId, fragment.isAdded()
? mFragmentManager.saveFragmentInstanceState(fragment) : null);
mFragments.remove(itemId);
}
mCurTransaction.remove(fragment);
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment)object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
mCurrentPrimaryItem.setUserVisibleHint(false);
}
if (fragment != null) {
fragment.setMenuVisibility(true);
fragment.setUserVisibleHint(true);
}
mCurrentPrimaryItem = fragment;
}
}
@Override
public void finishUpdate(ViewGroup container) {
if (mCurTransaction != null) {
mCurTransaction.commitNowAllowingStateLoss();
mCurTransaction = null;
}
}
@Override
public boolean isViewFromObject(View view, Object object) {
return ((Fragment)object).getView() == view;
}
@Override
public Parcelable saveState() {
Bundle state = null;
if (mSavedState.size() > 0) {
state = new Bundle();
for (int i=0; i< mSavedState.size(); i++) {
int itemId = mFragments.keyAt(i);
Fragment.SavedState ss = mSavedState.get(itemId);
if (ss != null) {
String key = "s" + itemId;
state.putParcelable(key, ss);
}
}
}
for (int i=0; i< mFragments.size(); i++) {
int itemId = mFragments.keyAt(i);
Fragment f = mFragments.get(itemId);
if (f != null && f.isAdded()) {
if (state == null) {
state = new Bundle();
}
String key = "f" + itemId;
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);
mSavedState.clear();
mFragments.clear();
Iterable 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) {
f.setMenuVisibility(false);
mFragments.put(index, f);
} else {
Log.w(TAG, "Bad fragment at key " + key);
}
}
else if (key.startsWith("s")) {
int index = Integer.parseInt(key.substring(1));
Parcelable parcelable = bundle.getParcelable(key);
if (parcelable instanceof Fragment.SavedState) {
mSavedState.put(index, (Fragment.SavedState) parcelable);
}
}
}
}
}
}