一、前言
在最近使用ViewPager2显示多个Fragment的时候遇到一个问题,就是我在删除某个fragment的时候发现更新适配器后页面并没有和如期的一样删除这个fragment,看下到底发生了什么?
二、问题剖析
一般我们刷新页面常用的方法是调用适配器的notifyDataSetChanged
方法,我们先看下ViewPager2的适配器更新方法都干啥了?
public class RecyclerView{public abstract static class Adapter<VH extends ViewHolder> {...这次省略部分代码public final void notifyDataSetChanged() {mObservable.notifyChanged();}}
}
可以看出,更新调用的是RecycleView的适配器方法,所以从这里我们也可以很直观的知道,ViewPager2跟ViewPager不一样的是使用的适配器不一样,ViewPager2是基于RecycleView的适配器策略基础上实现的,在这里就不细说了。然后接着查看源码,这里就贴一些关键的代码:
final LongSparseArray<Fragment> mFragments = new LongSparseArray<>();private void ensureFragment(int position) {//通过下标获取当前项的idlong itemId = getItemId(position);if (!mFragments.containsKey(itemId)) {// TODO(133419201): check if a Fragment provided here is a new FragmentFragment newFragment = createFragment(position);newFragment.setInitialSavedState(mSavedStates.get(itemId));//保存FragmentmFragments.put(itemId, newFragment);}
}
先看这个方法,从上面可以看出,适配器内部用LongSparseArray
变量存储Fragment,存入Fragment调用的是ensureFragment
方法,从方法上面可以看到,getItemId
作为key,createFragment
作为value存入到mFragments
当中,也就是说mFragments
通过键值对的形式存储Fragment,此后读取fragment,它的key是一个关键,那么看下它的key是怎么生成的?
public long getItemId(int position) {return position;
}
可以看出默认key就是当前item的下标。那么现在可以猜测到不刷新的原因可能是因为下标没有改变,所以新的fragment是存不进去的,且通过key,也就是下标获取到的fragment还是之前那一个fragment。为了验证我们的猜想,我们再看下一个方法updateFragmentMaxLifecycle
。
void updateFragmentMaxLifecycle(boolean dataSetChanged) {...此处省略部分代码//获取当前项的下标final int currentItem = mViewPager.getCurrentItem();if (currentItem >= getItemCount()) {/** current item is yet to be updated; it is guaranteed to change, so we will be* notified via {@link ViewPager2.OnPageChangeCallback#onPageSelected(int)} */return;}//获取当前项的id,如果上一次的long currentItemId = getItemId(currentItem);//如果获取的key还是之前那一个,则直接返回if (currentItemId == mPrimaryItemId && !dataSetChanged) {return; // nothing to do}//通过id获取当前的FragmentFragment currentItemFragment = mFragments.get(currentItemId);if (currentItemFragment == null || !currentItemFragment.isAdded()) {return;}//保存当前项的idmPrimaryItemId = currentItemId;FragmentTransaction transaction = mFragmentManager.beginTransaction();Fragment toResume = null;for (int ix = 0; ix < mFragments.size(); ix++) {long itemId = mFragments.keyAt(ix);Fragment fragment = mFragments.valueAt(ix);if (!fragment.isAdded()) {continue;}if (itemId != mPrimaryItemId) {transaction.setMaxLifecycle(fragment, STARTED);} else {toResume = fragment; // itemId map key, so only one can match the predicate}fragment.setMenuVisibility(itemId == mPrimaryItemId);}if (toResume != null) { // in case the Fragment wasn't added yettransaction.setMaxLifecycle(toResume, RESUMED);}if (!transaction.isEmpty()) {transaction.commitNow();}
}
从这个以看出,果然,首先获取当前的下标,如果当前的下标没有改变,则直接返回,这个判断验证了我之前删除第一个Fragment后为什么不刷新,它直接return了。再看下面直接对mFragments进行处理,到这里想必答案已经有了。所有的问题源头就是这个key,如果我们把生成key的规则改一下,让它不会像下标一样容易重复,那么问题解决了。
三、问题解决
其实只要保证生成的ItemId不重复就行,在这里我直接用fragment的hashCode值,到这里问题就解决了。
public class SimpleChipViewPage2Adapter extends FragmentStateAdapter {private List<Fragment> fragments;public SimpleChipViewPage2Adapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle, List<Fragment> fragments) {super(fragmentManager, lifecycle);this.fragments = fragments;}@NonNull@Overridepublic Fragment createFragment(int position) {return fragments == null ? null : fragments.get(position);}@Overridepublic int getItemCount() {return fragments == null ? 0 : fragments.size();}
//重载getItemId方法@Overridepublic long getItemId(int position) {return fragments == null ? 0 : fragments.get(position).hashCode();}
}