用过ListView和RecycleView的人都知道不管当前列表的浏览记录在哪里,只要重新setAdapter,列表就会重置,即从第一条item开始显示.
因此,想当然的我也就认为ViewPager也是这个样子的.结果并不是我想的那么简单,重复setAdapter并没什么卵用,Fragment的状态还是上一次所看到的样子,并没有重新初始化,Fragment的几什么周期方法一个都没有运行…实在无语…
经调试后发现ViewPager重新setAdapter并没有执行FragmentPagerAdapter的getItem方法,因此Adapter中的Fragment并不会重新初始化.
例如我的FragmentPagerAdapter :
class PageAdapter extends FragmentPagerAdapter {
public PageAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
Category c= mData.get(position % mData.size());
return NewsFragment.newInstance(c.id);
}
@Override
public int getCount() {
return mData.size();
}
}
既然getItem方法没有调用,那么就得看看其父类FragmentPagerAdapter 的源码了,看看getItem方法是在哪里调用的.发现是在父类的instantiateItem方法中调用的,源码如下:
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);//从缓存中查找该fragment
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position); //这里才会执行getItem方法
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;
}
注意观察Fragment fragment = mFragmentManager.findFragmentByTag(name);
这句代码,这里FragmentManager是通过tag别名的方式来从缓存中查找对应的Fragment,其tag的命名是有一套命名规范的,是通过ViewPager的id和getItemId的返回值来生成tag.
读到这里的时候,你应该就可以明白怎么做了,如果想要保证setAdapter方法会执行getItem方法,那么就得保证findFragmentByTag方法返回的Fragment是null的.
如何做到呢?
我这里想到2种解决思路:
1.重写FragmentPagerAdapter 的getItemId方法,每次都返回不同的id,这样就可以保证生成的tag别名每次都是不一样的,因此也无法从缓存中查找到值
2.清空mFragmentManager内的缓存.
我这里主要介绍的是第2种方法,即清空缓存的方式.
首先mFragmentManager,你得明白这个是什么,这个其实就是你创建FragmentPagerAdapter 的实现类的时候赋值的,一般是通过getSupportFragmentManager
或者getChildFragmentManager
方式得到的值,前者是在Activity中使用,后者是在Fragment中使用.
public abstract class FragmentPagerAdapter extends PagerAdapter {
private static final String TAG = "FragmentPagerAdapter";
private static final boolean DEBUG = false;
private final FragmentManager mFragmentManager;
private FragmentTransaction mCurTransaction = null;
private Fragment mCurrentPrimaryItem = null;
public FragmentPagerAdapter(FragmentManager fm) {
mFragmentManager = fm; //这里赋值
}
....
}
FragmentManager 是一个抽象类,其findFragmentByTag也是抽象的方法
public abstract Fragment findFragmentByTag(String tag);
因此只能找他的实现类了,即FragmentManagerImpl
final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory2 {..}
查看其findFragmentByTag的具体实现:
@Override
public Fragment findFragmentByTag(String tag) {
if (tag != null) {
// First look through added fragments.
for (int i=mAdded.size()-1; i>=0; i--) { //1
Fragment f = mAdded.get(i);
if (f != null && tag.equals(f.mTag)) {
return f;
}
}
}
if (mActive != null && tag != null) { //2
// Now for any known fragment.
for (int i=mActive.size()-1; i>=0; i--) {
Fragment f = mActive.valueAt(i);
if (f != null && tag.equals(f.mTag)) {
return f;
}
}
}
return null;
}
注意查看上面标注位置的1和2,分别有2个属性mAdded和mActive。
findFragmentByTag的操作就是操作这2个东东,那么这2个东东到底是啥呢,mAdded其实是一个ArrayList集合,而mActive就是一个SparseArray,观察源码便知真相
mAdded和mActive都各自维护了已经加载过的Fragment,也就是这里所说的缓存,既然这样,我就可以通过反射拿到这2个东东,然后调用它们的clear方法来清空缓存,那么findFragmentByTag方法拿到的结果就是null了.
下面看具体的逻辑,在setAdapter方法之前执行下面代码:
//先保证ViewPager之前已设置过Adapter,这样才有可能存在缓存
if (mContentVp.getAdapter() != null) {
//获取FragmentManager实现类的class对象,这里指的就是FragmentManagerImpl
Class<? extends FragmentManager> aClass = getChildFragmentManager().getClass();
try {
//1.获取其mAdded字段
Field f = aClass.getDeclaredField("mAdded");
f.setAccessible(true);
//强转成ArrayList
ArrayList<Fragment> list = (ArrayList) f.get(getChildFragmentManager());
//清空缓存
list.clear();
//2.获取mActive字段
f = aClass.getDeclaredField("mActive");
f.setAccessible(true);
//强转成SparseArray
SparseArray<Fragment> array = (SparseArray) f.get(getChildFragmentManager());
//清空缓存
array.clear();
} catch (Exception e) {
e.printStackTrace();
}
}
//再次设置ViewPager的Adapter
mContentVp.setAdapter(new PageAdapter(getChildFragmentManager()));
ok,至此大功告成.