Loading...
墨滴

掀乱书页的风

2021/10/15  阅读:66  主题:兰青

RecyclerView配合DiffUtil实现高效定向刷新

简介

自Android5.0以来,RecyclerView渐渐取代ListView成为Android开发中使用最多的列表控件,对于RecyclerView的使用相信大家都不陌生

DIffUtils 是 Support-v7:24:2.0 中,更新的工具类,实际已经出来很长时间了。

主要是为了配合RecyclerView 使用,通过比对新、旧两个数据集的差异,生成旧数据到新数据的最小变动,然后对有变动的数据项,进行局部刷新。

为什么会有DiffUtil

RecyclerView 自从被发布以来,一直被说成是 ListView、GridView 等一系列列表控件的完美替代品。并且它本身使用起来也非常的好用,布局切换方便、自带ViewHolder、局部更新并且可带更新动画等等。

局部更新、并且可以很方便的设置更新动画这一点,是 RecyclerView 一个不错的亮点。它为此提供了对应的方法:

adapter.notifyItemChange()
adapter.notifyItemInserted() adapter.notifyItemRemoved() adapter.notifyItemMoved() 以上方法都是为了对数据集中,单一项进行操作,并且为了操作连续的数据集的变动,还提供了对应的 notifyRangeXxx() 方法。

notifyItemRangeChanged notifyItemRangeInserted notifyItemRangeRemoved 虽然 RecyclerView 提供的局部更新的方法,看起来非常的好用,但是实际上并没有什么用。

在实际开发中,最方便的做法就是无脑调用 notifyDataSetChanged(),用于更新 adapter 的数据集。

虽然 notifyDataSetChanged 有一些缺点:

不会触发 RecyclerView 的局部更新的动画。 性能低,会刷新整个 RecyclerView 可视区域。 实际开发中,如果遇到需要频繁刷新前后两个数据集的场景,一个 notifyDataSetChanged() 方法,会比自己写一个数据集比对方法,然后去计算他们的差值,最后调用对应的方法更新到 RecyclerView 中去 简单的多。

对于懒人来讲,如果不是必要,当然是会选 notifyDataSetChanged() 了。估计是Google 也发现了这个问题,所以发布了 DiffUtil 。

RecycleView更新data普通用法

下面实现一个很普通的RecyclerView.Adapter

public class CommonAdapter extends RecyclerView.Adapter;CommonAdapter.DiffVH; {
    private List;
    Student; mDatas;
    private Context mContext;
    private LayoutInflater mInflater;

    public CommonAdapter(Context context, List;Student; mDatas) {
        this.mContext = context;
        this.mDatas = mDatas;
        mInflater = LayoutInflater.from(mContext);
    }

    @Override
    public DiffVH onCreateViewHolder(ViewGroup parent, int viewType) {
        return new DiffVH(mInflater.inflate(R.layout.item_diff, parent, false));
    }

    @Override
    public void onBindViewHolder(DiffVH holder, int position) {
        Log.i("DiffAdapter", "position:"+position);
        Student bean = mDatas.get(position);
        holder.tv_id.setText(Long.toString(bean.getId()));
        holder.tv_name.setText(bean.getName());
        holder.iv_pic.setImageResource(bean.getPic());
    }

    @Override
    public int getItemCount() {
        return mDatas.size();
    }

    class DiffVH extends RecyclerView.ViewHolder {
        TextView tv_id,tv_name;
        ImageView iv_pic;

        public DiffVH(View itemView) {
            super(itemView);
            tv_id = itemView.findViewById(R.id.tv_id);
            tv_name = itemView.findViewById(R.id.tv_name);
            iv_pic = itemView.findViewById(R.id.iv_pic);
        }
    }

}
Activity代码

public class CommonActivity extends AppCompatActivity {

    private List<Student> mDatas;
    private RecyclerView mRecyclerView;
    private CommonAdapter mAdapter;
    private Button refreshBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_common);
        mRecyclerView = findViewById(R.id.recycler_view);
        refreshBtn = findViewById(R.id.btn_refresh);

        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mDatas = initData();
        mAdapter = new CommonAdapter(this, mDatas);
        mRecyclerView.setAdapter(mAdapter);

        refreshBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onRefresh();
            }
        });
    }

    /**
     * 初始数据
     *
     * @return
     */
    private ArrayList<Student> initData() {
        ArrayList<Student> students = new ArrayList<>();
        students.add(new Student(1000,"刘备", R.drawable.pic1));
        students.add(new Student(1001,"关羽",  R.drawable.pic2));
        students.add(new Student(1002,"张飞",  R.drawable.pic3));
        students.add(new Student(1003,"赵云",  R.drawable.pic4));
        students.add(new Student(1004,"马超",  R.drawable.pic5));
        return students;
    }

    /**
     * 模拟刷新操作
     */
    public void onRefresh() {
        List<Student> newDatas = initData();

        newDatas.add(new Student(1005,"赵子龙", R.drawable.pic6));//模拟新增数据
        newDatas.get(0).setName("刘备(修改)");
        newDatas.get(1).setName("关羽(修改)");
        newDatas.get(0).setPic(R.drawable.pic7);//模拟修改数据
        Student student = newDatas.get(3);//模拟数据位移
        newDatas.remove(student);
        newDatas.add(student);

        mDatas.clear();
        mDatas.addAll(newDatas);
        mAdapter.notifyDataSetChanged();//我们大多数情况下都是这么写

    }
很简单,就是在数据改变的时候,调用notifyDataSetChanged,来更新recyclerView,包括我自己,一般也是这么写,如果使用DiffUtil怎么来写呢?

四.DiffUtil简单用法
我们需要先实现一个继承自DiffUtil.Callback的类,实现它的四个abstract方法,先介绍一下这个抽象类

public abstract static class Callback {
        public abstract int getOldListSize();//老数据集size
        public abstract int getNewListSize();//新数据集size
        public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition);//新老数据集在同一个postion的Item是否是一个对象?(可能内容不同,如果这里返回true,会调用下面的方法)
        public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition);//这个方法仅仅是上面方法返回ture才会调用,(例如微博的点赞,我们只需要刷新图标而不是整个Item)
        //该方法在DiffUtil进阶用法中用到
        @Nullable
        public Object getChangePayload(int oldItemPosition, int newItemPosition) {
            return null;
        }
    }
下面是我的实现方法

public class DiffCallBack extends DiffUtil.Callback {
    private List<Student> mOldDatas, mNewDatas;

    public DiffCallBack(List<Student> mOldDatas, List<Student> mNewDatas) {
        this.mOldDatas = mOldDatas;
        this.mNewDatas = mNewDatas;
    }

    //老数据集size
    @Override
    public int getOldListSize() {
        return mOldDatas != null ? mOldDatas.size() : 0;
    }

    //新数据集size
    @Override
    public int getNewListSize() {
        return mNewDatas != null ? mNewDatas.size() : 0;
    }

    /*
     * 被DiffUtil调用,用来判断 两个对象是否是相同的Item。
     * 例如,如果你的Item有唯一的id字段,这个方法就 判断id是否相等。
     * 本例判断name字段是否一致
     */
    @Override
    public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
		return mOldDatas.get(oldItemPosition).getId() == mNewDatas.get(newItemPosition).getId();
    }

    /*
     * 被DiffUtil调用,用来检查 两个item是否含有相同的数据
     * DiffUtil用返回的信息(true false)来检测当前item的内容是否发生了变化
     * 所以你可以根据你的UI去改变它的返回值
     * 例如,如果你用RecyclerView.Adapter 配合DiffUtil使用,你需要返回Item的视觉表现是否相同。
     * 这个方法仅仅在areItemsTheSame()返回true时,才调用。
     */
    @Override
	public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
    	Student beanOld = mOldDatas.get(oldItemPosition);
    	Student beanNew = mNewDatas.get(newItemPosition);
    	if (!beanOld.getName().equals(beanNew.getName())) {
        	return false;//如果有内容不同,就返回false
    	}
    	if (beanOld.getPic() != beanNew.getPic()) {
       	 	return false;//如果有内容不同,就返回false
    	}
    	return true; //默认两个data内容是相同的
}
}
然后在使用时,可以注释掉以前写的notifyDatasetChanged()方法了,替换成以下代码:

	DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallBack(mDatas, newDatas), true);
   	diffResult.dispatchUpdatesTo(mAdapter);

    //将新数据给Adapter
    mDatas.clear();
	mDatas.addAll(newDatas);
// mAdapter.notifyDataSetChanged();//大多数情况下我们都会这么写
五.使用步骤
在将newDatas 设置给Adapter之前,先调用DiffUtil.calculateDiff()方法,计算出新老数据集转化的最小更新集,就是DiffUtil.DiffResult对象。
DiffUtil.calculateDiff()方法定义如下:
第一个参数是DiffUtil.Callback对象,
第二个参数代表是否检测Item的移动,改为false算法效率更高,按需设置,我们这里是true。

public static DiffResult calculateDiff(Callback cb, boolean detectMoves)
 

然后利用DiffUtil.DiffResult对象的dispatchUpdatesTo()方法,传入RecyclerView的Adapter,替代mAdapter.notifyDataSetChanged()方法。
看下源码,这个方法内部,就是根据情况调用了adapter的四大定向刷新方法。

public void dispatchUpdatesTo(final RecyclerView.Adapter adapter) {
    dispatchUpdatesTo(new ListUpdateCallback() {
        @Override
        public void onInserted(int position, int count) {
            adapter.notifyItemRangeInserted(position, count);
        }

        @Override
        public void onRemoved(int position, int count) {
            adapter.notifyItemRangeRemoved(position, count);
        }

        @Override
        public void onMoved(int fromPosition, int toPosition) {
            adapter.notifyItemMoved(fromPosition, toPosition);
        }

        @Override
        public void onChanged(int position, int count, Object payload) {
            adapter.notifyItemRangeChanged(position, count, payload);
        }
    });
}

作为一名程序猿,只会简单用法,显然无法满足我们,下面看下进阶用法

六.DiffUtil的进阶用法 进阶用法只涉及到两个方法, 我们需要分别实现DiffUtil.Callback的 public Object getChangePayload(int oldItemPosition, int newItemPosition)方法,

返回的Object就是表示Item改变了哪些内容。

再配合RecyclerView.Adapter的 public void onBindViewHolder(VH holder, int position, List payloads)方法, 完成定向刷新。

这是一个新方法,注意它有三个参数,前两个我们熟,第三个参数就包含了我们在getChangePayload()返回的Object。

/*
部分(partial)绑定**vs完整(full)绑定 
payloads参数是一个从(notifyItemChanged(int, Object)或notifyItemRangeChanged(int, int, Object))里得到的合并list。 
如果payloads list 不为空,那么当前绑定了旧数据的ViewHolder 和Adapter,可以使用payload的数据进行一次 高效的部分更新。 
如果payload 是空的,Adapter必须进行一次完整绑定(调用两参方法)。 
Adapter不应该假定(想当然的认为) 在那些notifyxxxx通知方法传递过来的payload, 一定会在 onBindViewHolder()方法里收到。 
举例来说,当View没有attached 在屏幕上时,这个来自notifyItemChange()的payload 就简单的丢掉好了。 
payloads对象不会为null,但是它可能是空(empty),这时候需要完整绑定(所以我们在方法里只要判断isEmpty就好,不用重复判空)。 
*/
public void onBindViewHolder(VH holder, int position, List<Object> payloads) {
            onBindViewHolder(holder, position);
        }
 
从源码继续往下翻发现了一个bindViewHolder方法
 
public final void bindViewHolder(VH holder, int position) {
    holder.mPosition = position;
    if (hasStableIds()) {
        holder.mItemId = getItemId(position);
    }
    holder.setFlags(ViewHolder.FLAG_BOUND,
            ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID
                    | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
    TraceCompat.beginSection(TRACE_BIND_VIEW_TAG);
    onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());
    holder.clearPayload();
    final ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
    if (layoutParams instanceof RecyclerView.LayoutParams) {
        ((LayoutParams) layoutParams).mInsetsDirty = true;
    }
    TraceCompat.endSection();
}

这个方法仅仅就是调用了两个参数的构造方法,从源码继续往下翻发现了一个bindViewHolder方法, 在bindViewHolder方法中调用了三个参数的

onBindViewHolder方法,注释很多,当然这不是我翻译的,这是借鉴别人的。主要部分是两点 部分(partial)绑定**vs完整(full)绑定 如果payloads list 不为空,那么当前绑定了旧数据的ViewHolder 和Adapter, 可以使用 payload的数据进行一次 高效的部分更新。

如果payload 是空的,Adapter必须进行一次完整绑定(调用两参方法)。

payloads对象不会为null,但是它可能是空(empty),这时候需要完整绑定(所以我们在方法里只要判断isEmpty就好,不用重复判空)。 说了这么多,看下如何使用它,先看getChangePayload


/*
    * 当{@link #areItemsTheSame(int, int)} 返回true,且{@link #areContentsTheSame(int, int)} 返回false时,DiffUtils会回调此方法,
    * 去得到这个Item(有哪些)改变的payload。
    * 例如,如果你用RecyclerView配合DiffUtils,你可以返回  这个Item改变的那些字段,
    * {@link android.support.v7.widget.RecyclerView.ItemAnimator ItemAnimator} 可以用那些信息去执行正确的动画
    * 默认的实现是返回null
    * 返回 一个 代表着新老item的改变内容的 payload对象,
    */
  
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
   // 定向刷新中的部分更新 效率最高
   Student oldBean = mOldDatas.get(oldItemPosition);
   Student newBean = mNewDatas.get(newItemPosition);

   //这里就不用比较核心字段了,一定相等 (在本例中是id字段,其他场景比如帖子id之类)
   if (!oldBean.getName().equals(newBean.getName())) {
   	payload.putString("KEY_NAME", newBean.getName());
   }
   if (oldBean.getPic() != newBean.getPic()) {
  	 	payload.putInt("KEY_PIC", newBean.getPic());
   }

   if (payload.size() == 0){
   	//如果没有变化 就传空
   	return null;
   }
   return payload;
}

简单来讲,这个方法返回一个Object类型的payload,它包含了某个item的变化了的那些内容。 我们这里使用Bundle保存这些变化。

下面我们重写一下onBindViewHolder三参数方法


@Override
public void onBindViewHolder(DiffVH holder, int position, List<Object> payloads) {
   if (payloads.isEmpty()) {
       onBindViewHolder(holder, position);
   } else {
       Bundle payload = (Bundle) payloads.get(0);
       Student bean = mDatas.get(position);
       for (String key : payload.keySet()) {
           switch (key) {
               case "KEY_NAME":
   				//这里可以用payload里的数据,不过data也是新的 也可以用
   				holder.tv_name.setText(bean.getName());
  					break;
               case "KEY_PIC":
                   holder.iv_pic.setImageResource(payload.getInt(key));
                   break;
               default:
                   break;
           }
       }
   }
}

这里传递过来的payloads是一个List,由注释可知,一定不为null,所以我们判断是否是empty, 如果是empty,就调用两参的函数,进行一次Full Bind。 如果不是empty,就进行partial bind, 通过下标0取出我们在getChangePayload方法里返回的payload,然后遍历payload的key,根据key检索,如果payload里携带有相应的改变,就取出来 然后更新在ItemView上。

为什么是payloads.get(0)
经本人测试,List payloads 如果不为empty,每次调用时只有一个对象 ,具体可以查看

RecyclerView ------- onItemRangeChanged(int positionStart, int itemCount, Object payload) --->

AdapterHelper ----- onItemRangeChanged(int positionStart, int itemCount, Object payload), 此方法返回一个boolean值,就是这个List 长度是否为1, 根据这个布尔值,决定是否刷新界面(triggerUpdateProcessor()),具体的可以点进去看一下源码

至此,我们已经掌握了使用DiffUtil定向刷新RecyclerView的进阶用法。

DiffUtil 效率问题

DiffUtil内部采用的Eugene W. Myers’s difference 算法,但该算法不能检测移动的item,所以Google在其基础上改进支持检测移动项目,但是检测移动项目,会更耗性能。也就是移动只能被算作先删除、再增加,而DiffUtil是在算法的结果后再进行一次移动检查。假设在不检测元素移动的情况下,算法的时间复杂度为O(N + D2),而检测元素移动则复杂度为O(N2)。所以,如果集合本身就已经排好序,可以不进行移动的检测提升效率。 在有1000项数据,200处改动时,这个算法的耗时: 打开了移动检测时:平均值:27.07ms,中位数:26.92ms。 关闭了移动检测时:平均值:13.54ms,中位数:13.36ms。 如果我们的list过大,这个计算出DiffResult的时间还是比较久的,所以我们应该将获取DiffResult的过程放到子线程中,并在主线程中更新RecyclerView。

这还是有点麻烦啊,还要创建子线程,跟主线程进行交互,有没有更简单的用法呢,答案是有

DiffUtil的高级用法

为了方便这一操作,在support-v7:27.1.0又新增了一个DiffUtil的封装类,那就是AsyncListDiffer

如何来使用呢,第一步需要实现DiffUtil.ItemCallback这个抽象类,这里我直接贴代码了

public class DiffItemCallback extends DiffUtil.ItemCallback<Student> {

//是否是同一个对象
@Override
public boolean areItemsTheSame(Student oldItem, Student newItem) {
    return oldItem.getId() == newItem.getId();
}

//是否是相同内容
@Override
public boolean areContentsTheSame(Student oldItem, Student newItem) {
    return oldItem.getName().equals(newItem.getName()) &#x26;&#x26;
            oldItem.getPic() == newItem.getPic();
}

@Override
public Object getChangePayload(Student oldItem, Student newItem) {
    Bundle payload = new Bundle();
    if (!oldItem.getName().equals(newItem.getName())) {
        payload.putString("KEY_NAME", newItem.getName());
    }
    if (oldItem.getPic() != newItem.getPic()) {
        payload.putInt("KEY_PIC", newItem.getPic());
    }

    if (payload.size() == 0) {
        return null;
    }
    //如果没有变化 就传空
    return payload;
}

第二步 是将数据源交给AsyncListDiffer来处理,可以发现这个adapter是没有数据源的

public class DiffHighAdapter extends RecyclerView.Adapter<DiffHighAdapter.DiffVH> { // 数据的操作由AsyncListDiffer实现 private AsyncListDiffer<Student> mDiffer; private Context mContext; private LayoutInflater mInflater;

public DiffHighAdapter(Context context) {
    this.mContext = context;
	//初始化AsyncListDiffer,将我们实现的DiffItemCallback传递进去,
    mDiffer = new AsyncListDiffer&#x3c;&#x3e;(this, new DiffItemCallback());
    mInflater = LayoutInflater.from(mContext);
}

public void setDatas(List&#x3c;Student&#x3e; mDatas) {
	//submitList更新数据
    mDiffer.submitList(mDatas);
}

@Override
public DiffVH onCreateViewHolder(ViewGroup parent, int viewType) {
    return new DiffVH(mInflater.inflate(R.layout.item_diff, parent, false));
}

@Override
public void onBindViewHolder(DiffVH holder, int position) {
    Student bean = mDiffer.getCurrentList().get(position);
    holder.tv_id.setText(Long.toString(bean.getId()));
    holder.tv_name.setText(bean.getName());
    holder.iv_pic.setImageResource(bean.getPic());
}

@Override
public void onBindViewHolder(DiffVH holder, int position, List&#x3c;Object&#x3e; payloads) {
    if (payloads.isEmpty()) {
        onBindViewHolder(holder, position);
    } else {
        Bundle payload = (Bundle) payloads.get(0);
        Student bean = mDiffer.getCurrentList().get(position);
        for (String key : payload.keySet()) {
            switch (key) {
                case "KEY_NAME":
                    //这里可以用payload里的数据,不过data也是新的 也可以用
                    holder.tv_name.setText(bean.getName());
                    break;
                case "KEY_PIC":
                    holder.iv_pic.setImageResource(payload.getInt(key));
                    break;
                default:
                    break;
            }
        }
    }
}

@Override
public int getItemCount() {
    return mDiffer.getCurrentList().size();
}

class DiffVH extends RecyclerView.ViewHolder {
    TextView tv_id, tv_name;
    ImageView iv_pic;

    public DiffVH(View itemView) {
        super(itemView);
        tv_name = itemView.findViewById(R.id.tv_name);
        tv_id = itemView.findViewById(R.id.tv_id);
        iv_pic = itemView.findViewById(R.id.iv_pic);
    }
}

}

总结下AsyncListDiffer使用步骤

自实现DiffUtil.ItemCallback,给出item差异性计算条件 将所有对数据的操作代理给AsyncListDiffer,可以看到这个Adapter是没有List数据的 使用**submitList()**更新数据,并刷新ui,使用getCurrentList获取数据

Activity中如何调用呢

mAdapter.setDatas(newDatas),然后交给AsyncListDiffer来处理

下面看下AsyncListDiffer的线程处理

public void submitList(final List<T> newList) { if (newList == mList) { // nothing to do return; }

// incrementing generation means any currently-running diffs are discarded when they finish
final int runGeneration = ++mMaxScheduledGeneration;

// fast simple remove all
if (newList == null) {
    //新数据为空时清空列表
    int countRemoved = mList.size();
    mList = null;
    mReadOnlyList = Collections.emptyList();
    // notify last, after list is updated
    mUpdateCallback.onRemoved(0, countRemoved);
    return;
}

// fast simple first insert
//旧数据为空时添加数据
if (mList == null) {
    mList = newList;
    mReadOnlyList = Collections.unmodifiableList(newList);
    // notify last, after list is updated
    mUpdateCallback.onInserted(0, newList.size());
    return;
}

final List&#x3c;T&#x3e; oldList = mList;
//计算数据差异放在子线程
mConfig.getBackgroundThreadExecutor().execute(new Runnable() {
    @Override
    public void run() {
		// DiffResult  DiffUtil.Callback  有没有很熟悉
        final DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() {
            @Override
            public int getOldListSize() {
                return oldList.size();
            }

            @Override
            public int getNewListSize() {
                return newList.size();
            }

            @Override
            public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
                return mConfig.getDiffCallback().areItemsTheSame(
                        oldList.get(oldItemPosition), newList.get(newItemPosition));
            }

            @Override
            public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
                return mConfig.getDiffCallback().areContentsTheSame(
                        oldList.get(oldItemPosition), newList.get(newItemPosition));
            }

            @Nullable
            @Override
            public Object getChangePayload(int oldItemPosition, int newItemPosition) {
                return mConfig.getDiffCallback().getChangePayload(
                        oldList.get(oldItemPosition), newList.get(newItemPosition));
            }
        });
		//更新数据放在子线程
        mConfig.getMainThreadExecutor().execute(new Runnable() {
            @Override
            public void run() {
                if (mMaxScheduledGeneration == runGeneration) {
                    latchList(newList, result);
                }
            }
        });
    }
});

}

private void latchList(@NonNull List<T> newList, @NonNull DiffUtil.DiffResult diffResult) { mList = newList; // notify last, after list is updated mReadOnlyList = Collections.unmodifiableList(newList); //dispatchUpdatesTo方法,有没有很熟悉,调用四大定向更新方法 diffResult.dispatchUpdatesTo(mUpdateCallback); }

AsyncListDiffer更像是DiffUtil的工具类,到这里,我们已经掌握了DiffUtil刷新RecyclerView的使用了

总结

使用场景,RecyclerView定向刷新(个人认为,某些场景下的下拉刷新也可以使用),不过如果是简单做个删除 点赞这种,完全不用DiffUtils。自己记好postion,判断一下postion在不在屏幕里,调用那几个定向刷新的方法就可以了

其实DiffUtil不是只能和RecyclerView.Adapter配合使用, 我们可以自己实现 ListUpdateCallback接口,利用DIffUtil帮我们找到新旧数据集的最小差异集 来做更多的事情,这个就看大家自己发挥了

参考文档

https://blog.csdn.net/zxt0601/article/details/52562770

https://donniesky.me/2017/08/29/RxJava-And-DiffUtil/

https://www.jianshu.com/p/b9af71778b0d

掀乱书页的风

2021/10/15  阅读:66  主题:兰青

作者介绍

掀乱书页的风