首页 > 代码库 > Android开发Diffutils打造不一样的recyclerview

Android开发Diffutils打造不一样的recyclerview

简述

DiffUtil是recyclerview support library v7 24.2.0版本中新增的类,根据Google官方文档的介绍,DiffUtil的作用是比较两个数据列表并能计算出一系列将旧数据表转换成新数据表的操作。这个概念比较抽象,换一种方式理解,DiffUtil是一个工具类,当你的RecyclerView需要更新数据时,将新旧数据集传给它,它就能快速告知adapter有哪些数据需要更新。就相当于如果改变了就对某个item刷新,没改变就没刷新,可以简称为局部刷新。

无脑刷新VS局部刷新

首先我们需要知道DiffUtil使用Eugene W. Myers的Difference算法来计算出将一个数据集转换为另一个的最小更新量,也就是用最简单的方式将一个数据集转换为另一个。DiffUtil还可以识别一项数据在数据集中的移动。但该算法不能检测移动的item,所以Google在其基础上改进支持检测移动项目,但是检测移动项目,会更耗性能。 下面是谷歌官网给出的在Nexus 5X M系统上进行运算的时长:

  • 100项数据,10处改动:平均值0.39ms,中位数:0.35ms。
  • 100项数据,100处改动:
    • 打开了移位识别时:平均值:3.82ms,中位数:3.75ms。
    • 关闭了移位识别时:平均值:2.09ms,中位数:2.06ms。
  • 1000项数据,50处改动:
    • 打开了移位识别时:平均值:4.67ms,中位数:4.59ms。
    • 关闭了移位识别时:平均值:3.59ms,中位数:3.50ms。
  • 1000项数据,200处改动:
    • 打开了移位识别时:平均值:27.07ms,中位数:26.92ms。
    • 关闭了移位识别时:平均值:13.54ms,中位数:13.36ms。

使用姿势

首先,我们得学会如何使用它,第二,我们需要知道用什么姿势来使用它,姿势不对,全都白费。

Diffutils.Callback

我们先看下Diffutils的callback的源码:

 /**
     * A Callback class used by DiffUtil while calculating the diff between two lists.
     * 当使用Diffutils的时候,这是一个计算2list不同的回调函数
     */
    public abstract static class Callback {

        /**
         * Returns the size of the old list.
         * 得到老的数据源大小
         */
        public abstract int getOldListSize();

        /**
         * Returns the size of the new list.
         * 得到新的数据源大小
         */
        public abstract int getNewListSize();

        /**
         * Called by the DiffUtil to decide whether two object represent the same Item.
         * For example, if your items have unique ids, this method should check their id equality.
         * <p>
         * 被DiffUtil调用,用来判断 两个对象是否是相同的Item。
         * 例如,如果你的Item有唯一的id字段,这个方法就判断id是否相等。
         * @param oldItemPosition The position of the item in the old list
         *                        旧数据的item
         * @param newItemPosition The position of the item in the new list
         *                        新数据的item
         * @return True if the two items represent the same object or false if they are different.
         * true代表着2item内容相同,否则,不同
         */
        public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition);

        /**
         * Called by the DiffUtil when it wants to check whether two items have the same data.
         * DiffUtil uses this information to detect if the contents of an item has changed.
         * DiffUtil uses this method to check equality instead of {@link Object#equals(Object)}
         * so that you can change its behavior depending on your UI.
         * For example, if you are using DiffUtil with a
         * {@link android.support.v7.widget.RecyclerView.Adapter RecyclerView.Adapter}, you should
         * return whether the items‘ visual representations are the same.
         * This method is called only if {@link #areItemsTheSame(int, int)} returns
         * {@code true} for these items.
         * 被DiffUtil调用,用来检查 两个item是否含有相同的数据
         * DiffUtil用返回的信息(true/false)来检测当前item的内容是否发生了变化
         * 所以你可以根据你的UI去改变它的返回值
         * DiffUtil 用这个方法替代equals方法去检查是否相等。
         * 例如,如果你用RecyclerView.Adapter 配合DiffUtil使用,你需要返回Item的视觉表现是否相同。
         * 这个方法仅仅在areItemsTheSame()返回true时,才调用。
         * @param oldItemPosition The position of the item in the old list
         *                        旧数据的item
         * @param newItemPosition The position of the item in the new list which replaces the
         *                        oldItem
         *                        新数据某个替换了旧数据的item
         * @return True if the contents of the items are the same or false if they are different.
         * true代表着2item内容相同,否则,不同
         */
        public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition);
        /**
         * When {@link #areItemsTheSame(int, int)} returns {@code true} for two items and
         * {@link #areContentsTheSame(int, int)} returns false for them, DiffUtil
         * calls this method to get a payload about the change.
         * <p>
         * For example, if you are using DiffUtil with {@link RecyclerView}, you can return the
         * particular field that changed in the item and your
         * {@link android.support.v7.widget.RecyclerView.ItemAnimator ItemAnimator} can use that
         * information to run the correct animation.
         * <p>
         * Default implementation returns {@code null}.
         *
         * 当areItemsTheSame(int, int)返回true,且areContentsTheSame(int, int)返回false时,DiffUtils会回调此方法,
         * 去得到这个Item(有哪些)改变的payload。
         * 例如,如果你用RecyclerView配合DiffUtils,你可以返回  这个Item改变的那些字段,可以用那些信息去执行正确的动画
         * 默认的实现是返回null
         * @param oldItemPosition The position of the item in the old list
         * 在老数据源的postion
         * @param newItemPosition The position of the item in the new list
         * 在新数据源的position
         * @return A payload object that represents the change between the two items.
         * 返回 一个 代表着新老item的改变内容的 payload对象,
         */
        @Nullable
        public Object getChangePayload(int oldItemPosition, int newItemPosition) {
            return null;
        }
    }

上面是整个Callback的说明,我已标注中文,可以先理解下,我们接下来在看。

以正确姿势使用

上面我简单介绍了Callback的注解。现在我们通过继承来实现自己的。


public class SWDiffCallBack extends DiffUtil.Callback {

    private List<String> olddatas;
    private List<String> newDatas;

    public SWDiffCallBack(List<String> olddatas, List<String> newDatas) {
        this.olddatas = olddatas;
        this.newDatas = newDatas;
    }

    public int getOldListSize() {
        return olddatas.size();
    }

    public int getNewListSize() {
        return newDatas.size();
    }

    public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
        return olddatas.get(oldItemPosition).equals(newDatas.get(newItemPosition));
    }
    public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
        return olddatas.get(oldItemPosition).equals(newDatas.get(newItemPosition));
    }
}

简单的自定义Callback我们已经实现了,下面我们来看看,是如何使用的呢。

  newlist = new ArrayList<>();
                for (int i = 1; i < list.size(); i++) {
                    newlist.add(list.get(i) + "");
                }
                newlist.add(5,list.size() + j + "");
                j++;
                //普通刷新
                // list=newlist;
//                adapter.setList(newlist);
//                adapter.notifyDataSetChanged();
                //强大的局部刷新
                DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new SWDiffCallBack(list, newlist), true);
                //利用DiffUtil.DiffResult对象的dispatchUpdatesTo()方法,传入RecyclerView的Adapter
                //别忘了将新数据给Adapter
                list = newlist;
                adapter.setList(list);
                diffResult.dispatchUpdatesTo(adapter);

看起来比全局刷新代码多好多?不要紧,我们来看看它的效果图在说。

效果图

技术分享

一波666,还有自带的动画。我们在看看正常的全局刷新
技术分享

看起来比Diffutils简单很多,不过当你真正在网络请求使用的时候,会发现完全不一样,整个屏幕会闪一下~没错,就是闪一下。之前测试让我改这种bug,我也是有点蒙,没法改啊- - 除非重写。。所以,Diffutils还是很强大的,demo会在文末放出,你们可以自己下载跑跑看。

姿势进阶使用

我们看了之前的使用,是不是发现,我之前解决Callback的时候,和我写demo的时候,少了一个方法,没错,就是getChangePayload。这个是做什么的呢?就是我整个列表的某个item只有一个数据改变的时候,我只要去替换那一个数据,而不需要替换整个item。
好了,我们直接写个测试的demo。代码很简单:

public class SWLoadDiffCallBack extends SWDiffCallBack{
//    private List<Bean> oldList;
//    private List<Bean> newList;
    public SWLoadDiffCallBack(List<String> olddatas, List<String> newDatas) {
        super(olddatas, newDatas);
    }

    public Object getChangePayload(int oldItemPosition, int newItemPosition) {
//        Bean oldItem = oldList.get(oldItemPosition);
//        Bean newItem = newList.get(newItemPosition);
//        Bundle diffBundle = new Bundle();
//        if (!newItem.header.equals(oldItem.header)) {
//            diffBundle.putString(KEY_HEEDER, newItem.header);
//        }
//        if (!newItem.footer.equals(oldItem.footer)) {
//            diffBundle.putString(KEY_FOOTER, newItem.footer);
//        }
//        if (diffBundle.size() == 0)
            return null;
//        return diffBundle;
    }

这个我们是写个Bean,里面我们放入header和footer,然后进行逐个对比,这边我就不写demo了。相信这种写法你们应该能看懂。代码我已上传到csdn:点击下载demo

总结

1.Diffutils很适合各种刷新操作,我已经把他整合到我的开源中,你们可以自己去看。
2.Diffutils实现的局部刷新内部含有他自带的动画效果,所以我们无需去处理,而且看起来也比较美观~
3.DiffUtil可用于高效进行RecyclerView的数据更新,但DiffUtil本身的作用是计算数据集的最小更新。DiffUtil有强大的算法支撑,可以利用DiffUtil完成许多其他功能。

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    Android开发Diffutils打造不一样的recyclerview