版权声明:

本账号发布文章均来自公众号,承香墨影(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 配合 DiffUtil,好用到飞起的更多相关文章

  1. Android的Kotlin秘方(II):RecyclerView 和 DiffUtil

    作者:Antonio Leiva 时间:Sep 12, 2016 原文链接:http://antonioleiva.com/recyclerview-diffutil-kotlin/ 如你所知,在[支 ...

  2. Android局部更新(RecyclerView+ DiffUtil)

    一 概述 DiffUtil是support-v7:24.2.0中的新工具类,它用来比较两个数据集,寻找出旧数据集->新数据集的最小变化量. 说到数据集,相信大家知道它是和谁相关的了,就是我的最爱 ...

  3. 【RecyclerView优化】

    1.局部刷新 (1)避免整个列表的数据更新,只更新受影响的布局.例如,加载更多时,不使用notifyDataSetChanged(),而是使用notifyItemRangeInserted(range ...

  4. Android MultiType第三方库的基本使用和案例+DiffUtil的简单用法

    1.MultiType简单介绍 1.1.MultiType用于比较复杂的页面. 如下图,今日头条用到了MultiType处理各种复杂的页面.    这种还是比较简单的类型.因为一个页面也就这种类型. ...

  5. (转载)详解7.0带来的新工具类:DiffUtil

    [Android]详解7.0带来的新工具类:DiffUtil 标签: diffutil 2017-04-17 18:21 226人阅读 评论(0) 收藏 举报  分类: Android学习笔记(94) ...

  6. Android滑动列表(拖拽,左滑删除,右滑完成)功能实现(1)

    场景: 近期做的TODO APP需要在主页添加一个功能,就是可以左滑删除,右滑完成.看了一下当前其他人做的例如仿探探式的效果,核心功能基本一样,但是和我预想的还是有少量区别,于是干脆自己重头学一遍如何 ...

  7. 如何正确使用Espresso来测试你的Android程序

    UI测试在Android平台上一直都是一个令人头痛的事情, 由于大家平时用的很少, 加之很多文档的缺失, 如果很多东西从头摸索,势必踩坑无数. 自Android24正式淘汰掉了Instrumentat ...

  8. 注解:大话AOP与Android的爱恨情仇

    转载:大话AOP与Android的爱恨情仇 1. AOP与OOP的区别 平时我接触多的就是OOP(Object Oriented Programming面向对象).AOP(Aspect Oriente ...

  9. [Android Pro] AndroidX重构和映射

    原文地址:https://developer.android.com/topic/libraries/support-library/refactor https://blog.csdn.net/ch ...

随机推荐

  1. 再谈AbstractQueuedSynchronizer:基于AbstractQueuedSynchronizer的并发类实现

    公平模式ReentrantLock实现原理 前面的文章研究了AbstractQueuedSynchronizer的独占锁和共享锁,有了前两篇文章的基础,就可以乘胜追击,看一下基于AbstractQue ...

  2. Java 容器在实际项目开发中应用

    前言:在java开发中我们离不开集合数组等,在java中有个专有名词:"容器" ,下面会结合Thinking in Java的知识和实际开发中业务场景讲述一下容器在Web项目中的用 ...

  3. 关于Android log拿不到的情况

    遇到很多开发者说crash了,log没有看到..出现类似情况的基本原因是因为现在的国产厂商如crash了会直接将进程杀掉,于是你的studio就看不到了,可以往下面几个方向去想办法找到crash的lo ...

  4. LCS 算法实现

    动态规划算法 #include <iostream> #include <string.h> #include <algorithm> #include <m ...

  5. 基于GTID的Mysql-Mha高可用方案探索

    声明: 本篇文章内容整理来源于互联网以及本人自己的梳理总结,目的是从零到一的搭建起来mysql mha高可用架构. 一.软件概述 MHA(Master High Availability)目前在MyS ...

  6. vue mint UI

    vue 与mint  UI 结合开发手机app  html5页面 api  文档   http://mint-ui.github.io/#!/zh-cn

  7. 【学习笔记】C# 构造和析构

    构造方法 构造方法是一个特殊的方法,负责初始化对象 构造方法名必须和类名一致 构造方法没有返回值,但可以有参数,能够重载 构造方法可以不写,系统会自动为类添加一个无参的默认构造 如果将构造方法设置为P ...

  8. Android 应用退到后台

    Android 应用退到后台 2016-4-21 10:29:26 Android L moveTaskToBack(boolean nonRoot) 把包含这个Activity的任务转到后台.并不是 ...

  9. [luogu P3797] 妖梦斩木棒 [线段树]

    题目背景 妖梦是住在白玉楼的半人半灵,拥有使用剑术程度的能力. 题目描述 有一天,妖梦正在练习剑术.地面上摆放了一支非常长的木棒,妖梦把它们切成了等长的n段.现在这个木棒可以看做由三种小段构成,中间的 ...

  10. (转)Apache与Tomcat 区别联系

    Apache 和 Tomcat 都是web网络服务器,两者既有联系又有区别,在进行HTML.PHP.JSP.Perl等开发过程中,需要准确掌握其各自特点,选择最佳的服务器配置. Apache是web服 ...