RecyclerView 配合 DiffUtil,好用到飞起

版权声明:算法

本帐号发布文章均来自公众号,承香墨影(cxmyDev),版权归承香墨影全部。数据结构

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

未经容许,不得转载。布局

1、前言

DiffUtils 是 Support-v7:24:2.0 中,更新的工具类。由于已经更新了一段时间了,也很差说是最新更新的。性能

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

接下来就 DiffUtil 的使用细节,进行一个详细的讲解,但愿一篇文章就彻底理解 DiffUtil。动画

2、为何会有DiffUtil

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

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

  • adapter.notifyItemChange()
  • adapter.notifyItemInserted()
  • adapter.notifyItemRemoved()
  • adapter.notifyItemMoved();

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

虽然 RecyclerView 提供的局部更新的方法,看似很是的好用,可是实际上,其实并无什么用。

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

虽然 notifyDataSetChanged() 有一些缺点:

  • 不会触发 RecyclerView 的局部更新的动画。
  • 性能低,会刷新整个 RecyclerView 可视区域。

可是真有须要频繁刷新,先后两个数据集的场景。

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

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

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

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

3、介绍DiffUtil

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

固然,DiffUtil 不只只能配合 RecyclerView 使用,它实际上能够单独用于比对两个数据集,而后如何操做是能够定制的,那么在什么场景下使用,就全凭咱们本身发挥了。

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

  • DiffUtil.Callback:具体用于限定数据集比对规则。
  • DiffUtil.DiffResult:比对数据集以后,返回的差别结果。

一、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() 方法的。

二、DiffUtil.DiffResult

DiffUtil.DiffResult 其实就是 DiffUtil 经过 DiffUtil.Callback 计算出来,两个数据集的差别。它是能够直接使用在 RecyclerView 上的。若是有必要,也是能够经过实现 ListUpdateCallback 接口,来比对这些差别的。

三、使用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 的对应方法。

diff-dut

4、上例子

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

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

一、实现 DiffUtil.Callback

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

那么咱们开始实现 Callback:

diff-callback

二、切换数据集

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

diff-change

三、实现效果

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

5、DiffUtil 效率问题

既然 DiffUtil 很是的好用,而且内部也实现了一套算法,可是咱们也须要关心它的效率问题。

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

diff-duibi

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

Google 官方同时也指出,若是是对大数据集的比对,最好是方在子线程中去完成计算,也就是实际上是存在堵塞 UI 的状况的。因此若是你碰见了使用 DiffUtil 以后,每次刷新有卡顿的状况,能够考虑是否数据集太大,是否应该在子线程中完成计算。

6、结语

DiffUtil 已经介绍完了,若是以为本文对你有帮助。都看到这里了,点个赞再走吧。

公众号二维码.jpg

相关文章
相关标签/搜索