当前位置: 首页 > 科技观察

RecyclerViewwithDiffUtil,简单易用

时间:2023-03-15 10:09:55 科技观察

1.前言DiffUtils是Support-v7:24:2.0中更新的工具类。因为已经更新了一段时间,很难说是最新更新。主要与RecyclerView配合使用。它通过比较新旧数据集的差异,产生从旧数据到新数据的最小变化,然后局部刷新变化的数据项。接下来我将对DiffUtil的使用细节进行详细的讲解。希望一篇文章能够完整的了解DiffUtil。2、为什么会有DiffUtilRecyclerView自发布以来,据说是ListView、GridView等一系列列表控件的最佳替代品。而且使用起来也非常简单,布局切换方便,内置ViewHolder,局部更新和更新动画等。局部更新和能够方便地设置更新动画是RecyclerView的一个很好的亮点。为此它提供了相应的方法:adapter.notifyItemChange()adapter.notifyItemInserted()adapter.notifyItemRemoved()adapter.notifyItemMoved()以上方法都是针对数据集中的单个item进行操作,针对连续数据集进行操作对应的notifyRangeXxx()方法也被提供。RecyclerView提供的局部更新方法虽然看起来很好用,但实际上用处不大。在实际开发中,最方便的方式就是调用notifyDataSetChanged(),而不考虑去更新Adapter的数据集。虽然notifyDataSetChanged()有一些缺点:它不会触发RecyclerView局部更新的动画。低性能,会刷新整个RecyclerView可见区域。但是确实有需要频繁刷新的场景,前后两个数据集。解决方案1:使用notifyDataSetChanged()方法。方案二:自己写一个数据集比较方法,然后计算它们的差值,最后在Re??cyclerView中调用相应的方法进行更新。我很懒,如果没有必要,我当然会选择方案一。毕竟不比之前是ListView的时候差。Google显然也发现了这个问题,因此发布了DiffUtil。3.DiffUtil简介前面提到了DiffUtil就是为了解决这个痛点而设计的。它可以很容易地比较两个数据集,然后计算变化。配合RecyclerView.Adapter,可以根据变化自动调用Adapter对应的方法。当然,DiffUtil不仅仅可以和RecyclerView一起使用,它其实也可以单独用来比较两个数据集,而且具体如何操作是可以自定义的,所以在什么场景下使用就看我们自己了。使用DiffUtil时需要注意几个类:DiffUtil.Callback:专门用于限制数据集的比较规则。DiffUtil.DiffResult:比较数据集后,返回的差异结果。1、DiffUtil.CallbackDiffUtil.Callback主要是限制两个数据集中子项的比较规则。毕竟,开发人员面对的是各种各样的数据结构。由于没有办法做一个通用的内容对比方法,所以对比规则可以交给开发者实现。在Callback中,只需要实现4个方法:getOldListSize():旧数据集的长度。getNewListSize():新数据集的长度areItemsTheSame():判断是否是同一个Item。areContentsTheSame():如果是Item,该方法用于判断同一个Item的内容是否相同。前两个是获取数据集长度的方法,没什么好说的。不过后两种方式主要是针对多布局的情况,即有多个viewType和多个ViewHodlers的情况。首先需要使用areItemsTheSame()方法比较是否来自同一个viewType(即同一个ViewHolder),然后使用areContentsTheSame()方法比较内容是否相等。其实Callback还有一个getChangePayload()方法,可以在ViewType相同但内容不同的情况下,使用payLoad记录这个ViewHolder中需要更新的View。areItemsTheSame()、areContentsTheSame()、getChangePayload()分别代表不同幅度的刷新。首先会通过areItemsTheSame()判断当前位置的ViewType是否一致。如果不一致,说明在当前位置,从数据到UI结构都发生了变化,所以不用关心内容,直接更新即可。如果一致的话,这个View其实是可以复用的,需要用areContentsTheSame()方法来判断内容是否一致。如果一致,说明是同一条数据,不需要额外操作。但是一旦不一致,就会调用getChangePayload()标记不同的地方,最后标记需要更新的地方,最后返回DiffResult。当然,如果性能要求不是那么高,可以不使用getChangedPayload()方法。2、DiffUtil.DiffResultDiffUtil.DiffResult其实就是DiffUtil通过DiffUtil.Callback计算的两个数据集的差值。可以直接在RecyclerView上使用。如有必要,还可以通过实现ListUpdateCallback接口来比较这些差异。3、使用DiffUtil引入Callback和DiffResult后,其实可以正常使用DiffUtil比较数据集了。在这个过程中,其实很简单,只需要调用两个方法即可:calculateDiff方法主要是通过一个具体的DiffUtils.Callback实现对象来计算两个数据集的差异结果,得到DiffUtil.DiffResult。calculateDiff的另一个参数用于标记是否检测Item的移动。DiffUtil使用EugeneMyers的diff算法,该算法本身不检查元素的移动。也就是说,如果有元素在移动,只会先标记为删除,再标记为插入。而如果需要计算元素的移动,它实际上是在与EugeneMyers算法进行比较后进行移动检查。因此,如果集合本身已经排序,则可以跳过移动检查。而dispatchUpdatesTo()就是将数据集差异的结果通过Adapter更新到RecyclerView。其实dispatchUpdatesTo(Adapter)也是使用ListUpdateCallback接口,在这个接口中获取差值,然后调用Adapter对应的方法。4、上面的例子已经解释清楚了,下面开始例子。功能很简单,有四个数据集,由RecyclerView托管,然后有一个轮流切换数据集的按钮。1.实现DiffUtil.Callback为了简单起见,RecyclerView中使用了单一的ViewType,并使用了一个TextView来携带一个字符串进行显示。然后我们开始实现Callback:2.切换数据集既然DiffUtil.Callback已经实现了,我们需要处理切换数据集的点击事件。3.实现效果的关键代码贴出来了,其实很简单。最终运行效果如下:五、DiffUtil效率问题由于DiffUtil非常好用,内部实现了一套算法,所以我们也需要关心一下效率问题。根据Google官方文档给出的例子,在Nexus5XM系统上,DiffUtil的效率问题给出了一些参考数据:可以看出,其实DiffUtil的算法很好的解决了效率问题。在计算移动的情况下,1000条数据有200次修改,平均耗时仅为13.54毫秒,基本处于毫秒级别。谷歌官方还指出,如果是大数据集的对比,最好在子线程中完成计算,即实际上会出现UI被阻塞的情况。所以如果遇到使用DiffUtil后每次刷新都有卡顿的情况,可以考虑是不是数据集太大,是否应该在子线程中完成计算。6.结语DiffUtil已经介绍完了,如果你觉得这篇文章对你有帮助的话。看到这里了,点个赞,走起。【本文为专栏作家“张扬”原创稿件,转载请微信♂联系作者获得授权】点此查看作者更多好文