转载自琼珶和予
RecyclerView 源码分析(六)DiffUtil的差量算法分析
DiffUtil的差量算法分析
首先,估计有一部分的同学可能还不知道DiffUtil是什么,说实话,之前我也根本不了解这是什么东西。DiffUtil是我在公司实习的时候了解到的一个类,在那之前,我使用RecyclerView的方式也是大部分的人差不多,就是RecyclerView和它的四大组成部分任意组合。
当时在公司第一次看到这个东西的时候,立即两眼发光,非常好奇这是什么东西,就好像在大街上看到美女一样。后来在非工作时间的时候,我去了解了一下这个类,不过当时也只是简单的了解这个东西。现在在系统的学习RecyclerView的源码,我觉得有必要深入的了解和学习一下这个东西–DiffUtil。
本文参考资料:
Investigating Myers’ diff algorithm: Part 1 of 2
本文有一部分的内容来自上文的翻译。我的建议是,各位同学可以直接看上面的文章,大佬的文章已经将DiffUtil的核心算法讲的非常透彻。
本文打算从三个角度来分析DiffUtil
- DiffUtil的基本使用
- Myers差量算法的深入探究
- DiffUtil的Myers算法实现以及DiffUtil怎么跟Adapter联系起来的
1. 概述
在正式分析DiffUtil之前,我们先来对DiffUtil有一个大概的了解–DiffUtil到底是什么东西。
我们相信大家都遇到一种情况,那就是在一次操作里面可能会同时出现remove、add、change三种操作。像这种情况,我们不能调用notifyItemRemoved、notifyItemInserted或者notifyItemChanged方法,为了视图立即刷新,我们只能通过调用notifyDataSetChanged方法来实现。
而notifyDataSetChanged方法有什么缺点呢?没有动画!对,通过调用notifyDataSetChanged方法来刷新视图,每个操作是没有动画,这就很难受了!
有没有一种方式可以实现既能保留动画,又能刷新动画呢?我们单从解决问题的角度来说,我们可以设计一种算法,来比较变化前后的数据源有哪些变化,这里的变化包括,如上的三种操作。哪些位置进行了change操作,哪些地方进行了add操作,哪些地方进行了remove操作,可以通过这种算法计算出来。
Google爸爸考虑到这个问题大家都能遇到,那我帮你们实现,这样你们就不用自己去实现了,这就是DiffUtil的由来。
2. DiffUtil的基本使用
在正式分析DiffUtil的源码之前,我们先来看看DiffUtil的基本使用,然后我们从基本使用入手,这样看代码的时候才不会迷茫。
我们想要使用DiffUtil时,有一个抽象类Callback是我们必须了解的,我们来看看,了解它的每个方法都都有什么作用。
方法名 | 作用 |
---|---|
getOldListSize | 原数据源的大小 |
getNewListSize | 新数据源的大小 |
areItemsTheSame | 判断给定两个Item的是否同一个Item。给定的是两个Position,分别是原数据源的位置和新数据源的位置。判断两个Item是否是同一个Item,如果是不同的对象(新数据源和旧数据源持有的不是同一批对象,新数据源可能是从旧数据源那里深拷贝过来,也有重新进行网络请求返回的),可以给每个Item设置一个id,如果是同一个对象,可以直接使用==来判断 |
areContentsTheSame | 判断给定的两个Item内容是否相同。只有areItemsTheSame返回为true,才会回调此方法。也就是说,只能当两个Item是同一个Item,才会调用此方法来判断给定的两个Item内容是否相同。 |
getChangePayload | 用于局部刷新,回调此方法表示所给定的位置肯定进行change操作,所以这里不需要判断是否为change操作 |
简单的了解Callback每个方法的作用之后,我们现在来看看DiffUtil是怎么使用的。
我们先来看看ItemCallback是怎么实现的:
public class RecyclerItemCallback extends DiffUtil.Callback {
private List<Bean> mOldDataList;
private List<Bean> mNewDataList;
public RecyclerItemCallback(List<Bean> oldDataList, List<Bean> newDataList) {
this.mOldDataList = oldDataList;
this.mNewDataList = newDataList;
}
@Override
public int getOldListSize() {
return mOldDataList.size();
}
@Override
public int getNewListSize() {
return mNewDataList.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return Objects.equals(mNewDataList.get(newItemPosition).getId(), mOldDataList.get(oldItemPosition).getId());
}
@Override
public boolean areContentsTheSame(int i, int i1) {
return Objects.equals(mOldDataList.get(i).getContent(), mNewDataList.get(i1).getContent());
}
}
这里,areItemsTheSame方法是通过id来判断两个Item是不是同一个Item,其次areContentsTheSame方法是通过判断content来判断两个Item的内容是否相同。
然后,我们再来看看DiffUtil是怎么使用的:
private void refreshData() {
final List<Bean> oldDataList = new ArrayList<>();
final List<Bean> newDataList = mDataList;
// deep copy
for (int i = 0; i < mDataList.size(); i++) {