版权声明:算法
本帐号发布文章均来自公众号,承香墨影(cxmyDev),版权归承香墨影全部。数据结构
每周会统一更新到这里,若是喜欢,可关注公众号获取最新文章。工具
未经容许,不得转载。布局
DiffUtils 是 Support-v7:24:2.0 中,更新的工具类。由于已经更新了一段时间了,也很差说是最新更新的。性能
它主要是为了配合 RecyclerView 使用,经过比对新、旧两个数据集的差别,生成旧数据到新数据的最小变更,而后对有变更的数据项,进行局部刷新。大数据
接下来就 DiffUtil 的使用细节,进行一个详细的讲解,但愿一篇文章就彻底理解 DiffUtil。动画
RecyclerView 自从被发布以来,一直被说成是 ListView、GridView 等一系列列表控件的完美替代品。而且它自己使用起来也很是的好用,布局切换方便、自带 ViewHolder 、局部更新而且可带更新动画等等。ui
局部更新、而且能够很方便的设置更新动画这一点,是 RecyclerView 一个不错的亮点。它为此提供了对应的方法:spa
以上方法都是为了对数据集中,单一项进行操做,而且为了操做连续的数据集的变更,还提供了对应的 notifyRangeXxx()
方法。线程
虽然 RecyclerView 提供的局部更新的方法,看似很是的好用,可是实际上,其实并无什么用。
在实际开发中,最方便的作法就是无脑调用 notifyDataSetChanged()
,用于更新 Adapter 的数据集。
虽然 notifyDataSetChanged()
有一些缺点:
可是真有须要频繁刷新,先后两个数据集的场景。
方案一:使用一个 notifyDataSetChanged()
方法。
方案二:本身写一个数据集比对方法,而后去计算他们的差值,最后调用对应的方法更新到 RecyclerView 中去。
我这么懒,若是不是必要,固然是会选 方案一 了。毕竟和以前 ListView 的时候,也没有更差了。
Google 显然也发现了这个问题,因此 DiffUtil 被发布了。
就像前面说的,DiffUtil 就是为了解决这个痛点的。它能很方便的对两个数据集之间进行比对,而后计算出变更状况,配合 RecyclerView.Adapter ,能够自动根据变更状况,调用 Adapter 的对应方法。
固然,DiffUtil 不只只能配合 RecyclerView 使用,它实际上能够单独用于比对两个数据集,而后如何操做是能够定制的,那么在什么场景下使用,就全凭咱们本身发挥了。
DiffUtil 在使用起来,主要须要关注几个类:
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 经过 DiffUtil.Callback 计算出来,两个数据集的差别。它是能够直接使用在 RecyclerView 上的。若是有必要,也是能够经过实现 ListUpdateCallback 接口,来比对这些差别的。
介绍了 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 承载,而后有一个按钮,用于轮换的切换数据集。
为了简单,RecyclerView 中使用单一 ViewType ,而且使用一个 TextView 承载一个 字符串来显示。
那么咱们开始实现 Callback:
既然已经有了 DiffUtil.Callback 的实现以后,咱们就须要对切换数据集的点击事件进行处理了。
关键代码已经贴出来了,其实很是的简单,最终运行的效果以下:
既然 DiffUtil 很是的好用,而且内部也实现了一套算法,可是咱们也须要关心它的效率问题。
根据 Google 官方文档中给出的例子,在 Nexus 5X M 系统上,DiffUtil 的效率问题,给出了一些参考的数据:
能够看到,实际上,DiffUtil 的算法把效率问题解决的很是的好。在开启计算移动的状况下,1000 条数据中有 200 个修改,平均值也只有 13.54 ms ,基本上都是毫秒级的。
Google 官方同时也指出,若是是对大数据集的比对,最好是方在子线程中去完成计算,也就是实际上是存在堵塞 UI 的状况的。因此若是你碰见了使用 DiffUtil 以后,每次刷新有卡顿的状况,能够考虑是否数据集太大,是否应该在子线程中完成计算。
DiffUtil 已经介绍完了,若是以为本文对你有帮助。都看到这里了,点个赞再走吧。