ViewPager2+Fragment刷新失效的解决方案

一、前言

在最近使用ViewPager2显示多个Fragment的时候遇到一个问题,就是我在删除某个fragment的时候发现更新适配器后页面并没有和如期的一样删除这个fragment,看下到底发生了什么?

二、问题剖析

一般我们刷新页面常用的方法是调用适配器的notifyDataSetChanged方法,我们先看下ViewPager2的适配器更新方法都干啥了?

scala 复制代码
public class RecyclerView{

    public abstract static class Adapter<VH extends ViewHolder> {

          ...这次省略部分代码

          public final void notifyDataSetChanged() {
              mObservable.notifyChanged();
          }

    }
}

可以看出,更新调用的是RecycleView 的适配器方法,所以从这里我们也可以很直观的知道,ViewPager2ViewPager 不一样的是使用的适配器不一样,ViewPager2 是基于RecycleView的适配器策略基础上实现的,在这里就不细说了。然后接着查看源码,这里就贴一些关键的代码:

arduino 复制代码
final LongSparseArray<Fragment> mFragments = new LongSparseArray<>();

private void ensureFragment(int position) {
    //通过下标获取当前项的id
    long itemId = getItemId(position);
    if (!mFragments.containsKey(itemId)) {
        // TODO(133419201): check if a Fragment provided here is a new Fragment
        Fragment newFragment = createFragment(position);
        newFragment.setInitialSavedState(mSavedStates.get(itemId));
        //保存Fragment
        mFragments.put(itemId, newFragment);
    }
}

先看这个方法,从上面可以看出,适配器内部用LongSparseArray变量存储Fragment,存入Fragment调用的是ensureFragment方法,从方法上面可以看到,getItemId作为key ,createFragment作为value 存入到mFragments当中,也就是说mFragments通过键值对的形式存储Fragment,此后读取fragment,它的key是一个关键,那么看下它的key是怎么生成的?

arduino 复制代码
public long getItemId(int position) {
    return position;
}

可以看出默认key就是当前item的下标。那么现在可以猜测到不刷新的原因可能是因为下标没有改变,所以新的fragment是存不进去的,且通过key,也就是下标获取到的fragment还是之前那一个fragment 。为了验证我们的猜想,我们再看下一个方法updateFragmentMaxLifecycle

ini 复制代码
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获取当前的Fragment
    Fragment currentItemFragment = mFragments.get(currentItemId);
    if (currentItemFragment == null || !currentItemFragment.isAdded()) {
        return;
    }
    //保存当前项的id
    mPrimaryItemId = 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 yet
        transaction.setMaxLifecycle(toResume, RESUMED);
    }

    if (!transaction.isEmpty()) {
        transaction.commitNow();
    }
}

从这个以看出,果然,首先获取当前的下标,如果当前的下标没有改变,则直接返回,这个判断验证了我之前删除第一个Fragment后为什么不刷新,它直接return 了。再看下面直接对mFragments 进行处理,到这里想必答案已经有了。所有的问题源头就是这个key ,如果我们把生成key的规则改一下,让它不会像下标一样容易重复,那么问题解决了。

三、问题解决

其实只要保证生成的ItemId 不重复就行,在这里我直接用fragmenthashCode值,到这里问题就解决了。

java 复制代码
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
 @Override
 public Fragment createFragment(int position) {
     return fragments == null ? null : fragments.get(position);
 }

 @Override
 public int getItemCount() {
     return fragments == null ? 0 : fragments.size();
 }
//重载getItemId方法
 @Override
 public long getItemId(int position) {
     return fragments == null ? 0 : fragments.get(position).hashCode();
 }
}
相关推荐
mobsmobs8 小时前
Flutter开发环境搭建与工具链
android·flutter·ios·android studio·xcode
CheungChunChiu8 小时前
深入理解 eMMC RPMB 与 OP-TEE 在 Linux 系统中的应用开发
android·linux·运维·服务器·op-tee
onthewaying8 小时前
CameraX:Android相机开发的现代化解决方案
android
ifengouy8 小时前
Android中compileSdk,minSdk,targetSdk的含义和区别
android
星空梦想plus8 小时前
Android Camera openCamera
android·相机
茉莉玫瑰花茶11 小时前
MySQL 表的操作
android·数据库·mysql
CYRUS_STUDIO12 小时前
一键反编译、签名、安装 APK!手把手带你玩转 ApkTool + 签名工具
android·apk·逆向
只可远观12 小时前
Android集成Google Map
android·前端
搬砖不得颈椎病12 小时前
Compose 中的 Side-effects
android·android jetpack
fatiaozhang952712 小时前
天邑TY1613_S905L3SB_安卓9-高安版和非高安版-线刷固件包
android·电视盒子·刷机固件·机顶盒刷机