索鸟网

  1. 首页
  2. RecyclerView 配合 DiffUtil,好用到飞起

RecyclerView 配合 DiffUtil,好用到飞起


版权声明:

本账号发布文章均来自公众号,承香墨影(cxmyDev),版权归承香墨影所有。

每周会统一更新到这里,如果喜欢,可关注公众号获取最新文章。

未经允许,不得转载。

一、前言

DIffUtils 是 Support-v7:24:2.0 中,更新的工具类。因为已经更新了一段时间了,也不好说是最新更新的。

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

接下来就 DiffUtil 的使用细节,进行一个详细的讲解,希望一篇文章就完全理解 DiffUtil。

二、为什么会有DiffUtil

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

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

  • adapter.notifyItemChange()

  • adapter.notifyItemInserted()

  • adapter.notifyItemRemoved()

  • adapter.notifyItemMoved();

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

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

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

虽然 notifyDataSetChanged() 有一些缺点:

  • 不会触发 RecyclerView 的局部更新的动画。

  • 性能低,会刷新整个 RecyclerView 可视区域。

但是真有需要频繁刷新,前后两个数据集的场景。

方案一:使用一个 notifyDataSetChanged() 方法。

方案二:自己写一个数据集比对方法,然后去计算他们的差值,最后调用对应的方法更新到 RecyclerView 中去。

我这么懒,如果不是必要,当然是会选 方案一 了。毕竟和之前 ListView 的时候,也没有更差了。

Google 显然也发现了这个问题,所以 DiffUtil 被发布了。

三、介绍DiffUtil

就像前面说的,DiffUtil 就是为了解决这个痛点的。它能很方便的对两个数据集之间进行比对,然后计算出变动情况,配合 RecyclerView.Adapter ,可以自动根据变动情况,调用 Adapter 的对应方法。

当然,DiffUtil 不仅只能配合 RecyclerView 使用,它实际上可以单独用于比对两个数据集,然后如何操作是可以定制的,那么在什么场景下使用,就全凭我们自己发挥了。

DiffUtil 在使用起来,主要需要关注几个类:

  • DiffUtil.Callback:具体用于限定数据集比对规则。

  • DiffUtil.DiffResult:比对数据集之后,返回的差异结果。

1、DiffUtil.Callback

DiffUtil.Callback 主要就是为了限定两个数据集中,子项的比对规则。毕竟开发者面对的数据结构多种多样,既然没法做一套通用的内容比对方式,那么就将比对的规则,交还给开发者来实现即可。

在 Callback 中,其实只需要实现 4 个方法:

  • getOldListSize():旧数据集的长度。

  • getNewListSize():新数据集的长度

  • areItemsTheSame():判断是否是同一个Item。

  • areContentsTheSame():如果是通一个Item,此方法用于判断是否同一个 Item 的内容也相同。

前两个是获取数据集长度的方法,这没什么好说的。但是后两个方法,主要是为了对应多布局的情况产生的,也就是存在多个 viewType 和多个 ViewHodler 的情况。首先需要使用 areItemsTheSame() 方法比对是否来自同一个 viewType(也就是同一个 ViewHolder ) ,然后再通过 areContentsTheSame() 方法比对其内容是否也相等。

其实 Callback 还有一个 getChangePayload() 的方法,它可以在 ViewType 相同,但是内容不相同的时候,用 payLoad 记录需要在这个 ViewHolder 中,具体需要更新的View。

areItemsTheSame()areContentsTheSame()getChangePayload() 分别代表了不同量级的刷新。

首先会通过 areItemsTheSame() 判断当前 position 下,ViewType 是否一致,如果不一致就表明当前 position 下,从数据到 UI 结构上全部变化了,那么就不关心内容,直接更新就好了。如果一致的话,那么其实 View 是可以复用的,就还需要再通过 areContentsTheSame() 方法判断其内容是否一致,如果一致,则表示是同一条数据,不需要做额外的操作。但是一旦不一致,则还会调用 getChangePayload() 来标记到底是哪个地方的不一样,最终标记需要更新的地方,最终返回给 DiffResult 。

当然,对性能要是要求没那么高的情况下,是可以不使用 getChangedPayload() 方法的。

2、DiffUtil.DiffResult

DiffUtil.DiffResult 其实就是 DiffUtil 通过 DiffUtil.Callback 计算出来,两个数据集的差异。它是可以直接使用在 RecyclerView 上的。如果有必要,也是可以通过实现 ListUpdateCallback 接口,来比对这些差异的。

3、使用DiffUtil

介绍了 Callback 和 DiffResult 之后,其实就可以正常使用 DiffUtil 来进行数据集的比对了。

在这个过程中,其实真的很简单,只需要调用两个方法:

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

calculateDiff 方法主要是用于通过一个具体的 DiffUtils.Callback 实现对象,来计算出两个数据集差异的结果,得到 DiffUtil.DiffResult

而 calculateDiff 的另外一个参数,用于标记是否需要检测 Item 的移动。

DiffUtil 使用的是 Eugene Myers 的差别算法,这个算法本身是不检查元素的移动的。也就是说,有元素的移动它也只是会先标记为删除,然后再标记插入。而如果需要计算元素的移动,它实际上也是在通过 Eugene Myers 算法比对之后,再进行一次移动检查。所以,如果集合本身已经排序过了,可以不进行移动的检查。

dispatchUpdatesTo() 就是将这个数据集差异的结果,通过 Adapter 更新到 RecyclerView 上面。

实际上 dispatchUpdatesTo(Adapter) ,也是使用的 ListUpdateCallback 这个接口,在其中获得差异,然后调用 Adapter 的对应方法。

四、上例子

既然已经说清楚了,那么我们开始上例子了。

功能很简单,有四个数据集,使用 RecyclerView 承载,然后有一个按钮,用于轮换的切换数据集。

1、实现 DiffUtil.Callback

为了简单,RecyclerView 中使用单一 ViewType ,并且使用一个 TextView 承载一个 字符串来显示。

那么我们开始实现 Callback:

2、切换数据集

既然已经有了 DiffUtil.Callback 的实现之后,我们就需要对切换数据集的点击事件进行处理了。

3、实现效果

关键代码已经贴出来了,其实非常的简单,最终运行的效果如下:

五、DiffUtil 效率问题

既然 DiffUtil 非常的好用,并且内部也实现了一套算法,但是我们也需要关心它的效率问题。

根据 Google 官方文档中给出的例子,在 Nexus 5X M 系统上,DiffUtil 的效率问题,给出了一些参考的数据:

可以看到,实际上,DiffUtil 的算法把效率问题解决的非常的好。在开启计算移动的情况下,1000 条数据中有 200 个修改,平均值也只有 13.54 ms ,基本上都是毫秒级的。

Google 官方同时也指出,如果是对大数据集的比对,最好是方在子线程中去完成计算,也就是其实是存在堵塞 UI 的情况的。所以如果你遇见了使用 DiffUtil 之后,每次刷新有卡顿的情况,可以考虑是否数据集太大,是否应该在子线程中完成计算。

六、结语

DiffUtil 已经介绍完了,如果觉得本文对你有帮助。都看到这里了,点个赞再走吧。

recyclerview

来源地址:https://segmentfault.com/a/1190000010722635 版权归作者所有!

相关教程

  • Android RecyclerView与CardView的配合使用

    首先 我要介绍,我是学习的鸿洋大神的博客,然后写的这个,有兴趣的可以看一下,大神介绍的很详细,连接如下: http://blog.csdn.net/lmj623565791/article/details/45059587 首先看一下效果图: 第一步:导入所需要的包 /*首先到导入这两个包*/ compile "com.android.supp
  • Android版本介绍 - 收藏集 - 掘金

    关于 Android 7.0 适配中 FileProvider 部分的总结 - 掘金由于 Android 7.0 或更高版本的系统在国内手机市场上的占比不是很高,很多 Android 开发人员并没有做 7.0 适配工作,同时测试人员也容易忽视这方面的兼容问题。这导致 7.0 及以上版本的手机用户在使用到应用部分功能时可能出现 App 崩溃闪退... Pe
  • RecyclerView初级

    第一步:导入依赖 compile "com.android.support:recyclerview-v7:24.0.0" 第二步:代码及布局 在布局中使用: <android.support.v7.widget.RecyclerView android:id="@+id/recycleview"
  • 九月了,回头看看八月你错过的原创好文

    版权声明: 本账号发布文章均来自公众号,承香墨影(cxmyDev),版权归承香墨影所有。 每周会统一更新到这里,如果喜欢,可关注公众号获取最新文章。 未经允许,不得转载。 今天是周六,对于大多数人而言,预示着这周的工作已经结束,但是对于程序员而言,学习进步是永不停歇的。 今天也是九月第一个周末,学校都已经开学了,一位中学校长说过这样一段话: 开学不是
  • Material Design - 收藏集 - 掘金

    DrawerLayout 和 NavigationView 使用详解 - Android - 掘金Android Material Design Library 推出了很长时间,越来越多的APP使用了符合Library 包的控件,DrawerLayout绝对是热门之一,Material Design定义了一个抽屉导航应该有何种外观和感受,统一了侧滑菜单和
  • mock大法好

    mock大法好 mock是一个模拟数据生成器,旨在帮助前端独立于后端进行开发,帮助编写单元测试。mock有如下功能 根据数据模板生成模板数据 模拟ajax请求,生成请求数据 基于html模板生成模拟数据 下载安装 npm install mockjs //使用mock var Mock = require("mockjs"); var mcok =
  • RecyclerView的使用

    代码 public class MainActivity extends AppCompatActivity {     RecyclerView rv;     List<String> list;     int w_screen;     in
  • RecyclerView高级用法

    之前写了个RecyclerView的使用方法与如何设置点击监听,后来群里有个人问我如何在点击item的时候为item添加图片,并在点击其他item的时候,新的item里添加图片,之前的item里的图片消失。听起来很简单,但实际。。。网上找了点例子看了下,都不太满意,而且写的比较繁琐,还是自己动手吧。。。点击后给item添加图片,这个简单,但是点击下一个item的时候如何让上一个i