
每个Android开发人员都使用RecyclerView
来显示列表,并且每个人都面临着更新列表中数据的问题,直到2016年魔术类DiffUtil
出现为止。 我将用手指解释它实际上是如何工作的,并尝试消除他的魔力。
一点历史
列表是移动应用程序中最常见的元素之一,在我们的例子中是RecyclerView
。 这些可以是任何内容的列表:办公地址,社交网络中的朋友列表。 网络,出租车应用中的订单历史记录等。 所有这些情况都与不断将列表中的数据更改为新数据的需求结合在一起,例如,当用户执行Swipe刷新,过滤列表或以其他任何方式从背面接收到一包新数据时。
为了实现此行为,现代Android开发人员的祖先手动选择了哪些数据以及如何更改,然后从RecyclerView
调用了适当的方法。 但是,当Google发布支持库版本25.1.0并在其中添加DiffUtil
时,一切都发生了变化,这使您可以将旧列表神奇地转换为新列表,而无需完全重建RecyclerView
。 在本文中,我将消除DiffUtil
的魔力并解释其工作原理。
如何使用DiffUtil?
要使用DiffUtil
必须实现DiffUtil.Callback
,调用calculateDiff(@NonNull Callback cb)
方法calculateDiff(@NonNull Callback cb)
然后使用dispatchUpdatesTo()
方法将接收到的DiffResult
应用于RecyclerView
。 calucalteDiff(@NonNull Callback cd)
方法时会发生什么? 此方法返回DiffResult
,其中包含一组将原始列表转换为新列表的操作。 通过调用notifyItemRangeInserted
, notifyItemRangeRemoved
, notifyItemMoved
和notifyItemRangeChanged
来应用更新。 前三个方法更改列表的结构,即元素的位置,而不更改元素本身,并且不对它们调用onBindViewHolder()
(添加元素除外)。 后者更改元素本身,并调用onBindViewHolder()
更改元素的视图。
DiffUtil
使用Myers算法检查两个列表是否存在差异,该算法仅确定是否存在更改,但不知道如何查找元素的移动。 为此, DiffUtil
遍历由Myers算法创建的DiffUtil
(稍后会详细介绍),然后搜索运动。 DiffResult
的形成
如果算法不检查元素的运动,并且
,其中P是已添加和已删除元素的数量。
迈尔斯算法
接下来,将考虑对手指上的Myers算法的解释,在该文章的结尾将有对该算法的数学解释的链接(以及该主题的其他出色文章)。 考虑两个序列:BACAAC和CBCBAB。 有必要在第一个序列上编写一个转换序列,然后得到第二个。 我们将表中的序列编写如下:旧列表表示列的第一个元素,新列表表示行的第一个元素。

划掉两个序列的相同元素相交的单元格:

另一个任务是以最少的步骤从矩阵的左上角到达右下角。 您可以沿水平和垂直面移动。 如果到达对角线的起点,则必须沿对角线移动,但是此步的成本为0。因此,沿边的一步的成本为1。

从点(0; 0)开始,我们可以向右和向下移动。 向下移动时,您还必须对角走。 一步中的动作称为蛇,在这种情况下,收到了2条蛇:(0; 0)->(0; 1)和(0; 0)->(1; 2)。 箭头表示蛇的末端,即 如果在垂直/水平步骤之后沿对角线有一个必填步骤,则箭头将在对角线上。 从起点到终点,蛇的完整结构如下所示。 视频中的某些路径被省略,因为 显然不是最短的。

结果,我们得到了几种可能的最短路径,其中一些显示在下面。


从最左边到最右边传递矩阵如何帮助确定将一个序列转换为另一个序列的动作序列(脚本)? 水平,垂直和对角线台阶是什么意思? 在可能的方向之一上沿着矩阵的一步是对旧行的操作:
- 水平步骤-从旧行删除
- 垂直步骤-添加到旧行
- 对角台阶-不变
以第二条路径为例,我们比较该路径和生成的脚本。 第一步是垂直的,这意味着我们将字符“ C”添加到旧行的0位置。

但是,这并不是整条蛇。 接下来,我们必须沿对角线移动。 当沿对角线移动时,元素B保持不变。 结果,蛇由垂直运动+对角运动组成。

接下来,将蛇水平放置-从旧行中删除元素A。

该视频显示了从头到尾的整个路径,其中源字符串发生了变化,直到转换为最终路径为止。

Myers算法的结果是一个脚本,其中包含一组将一个序列转换为另一个序列必须执行的最小操作数。 在DiffUtil
Myers算法用于搜索由areItemsTheSame()
方法确定的不同元素。 除了形成蛇的列表外,Myers算法还会在通过列表时创建旧列表和新列表中元素的状态列表。 所有这些数据以及用户实现的detectMoves
标志和回调都传递给DiffResult(Callback callback, List<Snake> snakes, int[] oldItemStatuses, int[] newItemStatuses, boolean detectMoves)
构造函数DiffResult(Callback callback, List<Snake> snakes, int[] oldItemStatuses, int[] newItemStatuses, boolean detectMoves)
。
在撰写本文时,我能够发掘DiffResult
到底发生了DiffResult
:该算法遍历蛇并为元素设置了标志(在状态列表中),这确定了元素到底发生了什么。 使用这些标志,将更改应用于RecyclerView
确定将更新应用于以下方法: notifyItemRangeInserted, notifyItemRangeRemoved, notifyItemMoved notifyItemRangeChanged
。 下次,我将更详细地讨论这一点。
参考文献