DiffUtil

作者: code希必地 | 来源:发表于2021-10-14 17:04 被阅读0次

    1、前言

    DIffUtils 是 Support-v7:24:2.0 中,更新的工具类。

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

    当然,DiffUtil 不仅只能配合 RecyclerView 使用,它实际上可以单独用于比对两个数据集,然后如何操作是可以定制的,那么在什么场景下使用,就全凭我们自己发挥了。

    2、DiffUtil

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

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

    2.1、DiffUtil.Callback

    DiffUtil.Callback 主要就是为了限定两个数据集中,子项的比对规则。毕竟开发者面对的数据结构多种多样,既然没法做一套通用的内容比对方式,那么就将比对的规则,交还给开发者来实现即可。

    在 Callback 中,其实只需要实现 4 个方法:

    • getOldListSize():旧数据集的长度。
    • getNewListSize():新数据集的长度
    • areItemsTheSame():判断是否是同一个Item。
    • areContentsTheSame():如果是通一个Item,此方法用于判断是否同一个 Item 的内容也相同。

    前两个是获取数据集长度的方法,这没什么好说的。但是后两个方法,主要是为了对应多布局的情况产生的,也就是存在多个 viewType的情况。首先需要使用 areItemsTheSame() 方法比对是否来自同一个 viewType ,然后再通过 areContentsTheSame() 方法比对其内容是否也相等。

    其实 Callback 还有一个 getChangePayload() 的方法,它可以在 ViewType 相同,但是内容不相同的时候,用 payLoad 记录需要在这个 ViewHolder 中,然后去更新具体的View。

    areItemsTheSame()、areContentsTheSame()、getChangePayload() 分别代表了不同量级的刷新。

    首先会通过 areItemsTheSame()判断当前 position 下的ViewType 是否一致,如果不一致就表明当前 position 下,从数据到 UI 结构上全部变化了,那么就不关心内容(areContentsTheSame()、getChangePayload()不会被调用),直接更新就好了。如果一致的话,那么其实 View 是可以复用的,就还需要再通过 areContentsTheSame() 方法判断其内容是否一致,如果一致,则表示是同一条数据,不需要做额外的操作。但是一旦不一致,则还会调用 getChangePayload() 来标记到底是哪个地方的不一样,最终标记需要更新的地方,最终返回给 DiffResult 。

    当然,对性能要是要求没那么高的情况下,是可以不使用 getChangedPayload() 方法的。

    2.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 的移动。
    dispatchUpdatesTo() 就是将这个数据集差异的结果,通过 Adapter 更新到 RecyclerView 上面。
    实际上 dispatchUpdatesTo(Adapter) ,也是使用的 ListUpdateCallback 这个接口,在其中获得差异,然后调用 Adapter 的对应方法。

    public void dispatchUpdatesTo(final RecyclerView.Adapter adapter) {
        dispatchUpdatesTo(new ListUpdateCallback() {
            @Override
            public void onInserted(int position, int count) {
                adapter.notifyItemRangeInserted(position, count);
            }
    
            @Override
            public void onRemoved(int position, int count) {
                adapter.notifyItemRangeRemoved(position, count);
            }
    
            @Override
            public void onMoved(int fromPosition, int toPosition) {
                adapter.notifyItemMoved(fromPosition, toPosition);
            }
    
            @Override
            public void onChanged(int position, int count, Object payload) {
                adapter.notifyItemRangeChanged(position, count, payload);
            }
        });
    }
    

    4、实例

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

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

    4.1、实现 DiffUtil.Callback

    public class AdapterDiffCallBack extends DiffUtil.Callback {
        private ArrayList<Bean> oldData;
        private ArrayList<Bean> newData;
    
        public AdapterDiffCallBack(ArrayList<Bean> oldData, ArrayList<Bean> newData) {
            this.oldData = oldData;
            this.newData = newData;
        }
    
        @Override
        public int getOldListSize() {
            return oldData.size();
        }
    
        @Override
        public int getNewListSize() {
            return newData.size();
        }
    
        @Override
        public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
            return oldData.get(oldItemPosition).getType() == newData.get(newItemPosition).getType();
        }
    
        @Override
        public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
            return oldData.get(oldItemPosition).equals(newData.get(newItemPosition));
        }
    
        @Nullable
        @Override
        public Object getChangePayload(int oldItemPosition, int newItemPosition) {
            return super.getChangePayload(oldItemPosition, newItemPosition);
        }
    }
    

    4.2、切换数据集

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

    public void changeData(View view) {
        final ArrayList<Bean> oldData = dataArray[index];
        index++;
        index = index % dataArray.length;
        final ArrayList<Bean> newData = dataArray[index];
        mDatas1 = newData;
    
        DiffUtil.DiffResult result = DiffUtil.calculateDiff(new AdapterDiffCallBack(oldData,newData), false);
        result.dispatchUpdatesTo(recyclerView.getAdapter());
    }
    

    注意:Google 官方同时也指出,如果是对大数据集的比对,最好是方在子线程中去完成计算,也就是其实是存在堵塞 UI 的情况的。所以如果你遇见了使用 DiffUtil 之后,每次刷新有卡顿的情况,可以考虑是否数据集太大,是否应该在子线程中完成计算。

    相关文章

      网友评论

          本文标题:DiffUtil

          本文链接:https://www.haomeiwen.com/subject/mvruoltx.html