Android7.0 工具类:DiffUtil详解

一 概述

DiffUtil是support-v7:24.2.0中的新工具类,它用来比较两个数据集,寻找出旧数据集-》新数据集的最小变化量。

说到数据集,相信大家知道它是和谁相关的了,就是我的最爱,RecyclerView。

就我使用的这几天来看,它最大的用处就是在RecyclerView刷新时,不再无脑mAdapter.notifyDataSetChanged()。

以前无脑mAdapter.notifyDataSetChanged()有两个缺点:

1.不会触发RecyclerView的动画(删除、新增、位移、change动画)

2.性能较低,毕竟是无脑的刷新了一遍整个RecyclerView,极端情况下:新老数据集一模一样,效率是最低的。

使用DiffUtil后,改为如下代码:

DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallBack(mDatas,newDatas),true);
diffResult.dispatchUpdatesTo(mAdapter);

它会自动计算新老数据集的差异,并根据差异情况,自动调用以下四个方法

adapter.notifyItemRangeInserted(position,count);
adapter.notifyItemRangeRemoved(position,count);
adapter.notifyItemMoved(fromPosition,toPosition);
adapter.notifyItemRangeChanged(position,count,payload);

显然,这个四个方法在执行时都是伴有RecyclerView的动画的,且都是定向刷新方法,刷新效率蹭蹭的上升了。
老规矩,先上图,

图一是无脑mAdapter.notifyDataSetChanged()的效果图,可以看到刷新交互很生硬,Item突然的出现在某个位置:

图二是使用DiffUtils的效果图,最明显的是有插入、移动Item的动画:

转成GIF有些渣,下载文末Demo运行效果更佳哦。

本文将包含且不仅包含以下内容:

1 先介绍DiffUtil的简单用法,实现刷新时的“增量更新”效果。(“增量更新”是我自己的叫法)
2 DiffUtil的高级用法,在某项Item只有内容(data)变化,位置(position)未变化时,完成部分更新(官方称之为Partial bind,部分绑定)。
3 了解到 RecyclerView.Adapter还有public void onBindViewHolder(VH holder,int position,List<Object> payloads)方法,并掌握它。
4 在子线程中计算DiffResult,在主线程中刷新RecyclerView。
5 少部分人不喜欢的notifyItemChanged()导致Item白光一闪的动画 如何去除。
6 DiffUtil部分类、方法 官方注释的汉化

二 DiffUtil的简单用法

前文也提到,DiffUtil是帮助我们在刷新RecyclerView时,计算新老数据集的差异,并自动调用RecyclerView.Adapter的刷新方法,以完成高效刷新并伴有Item动画的效果。

那么我们在学习它之前要先做一些准备工作,先写一个普通青年版,无脑notifyDataSetChanged()刷新的Demo。

1 一个普通的JavaBean,但是实现了clone方法,仅用于写Demo模拟刷新用,实际项目不需要,因为刷新时,数据都是从网络拉取的。:

class TestBean implements Cloneable {
 private String name;
 private String desc;
 ....//get set方法省略
 //仅写DEMO 用 实现克隆方法
 @Override
 public TestBean clone() throws CloneNotSupportedException {
  TestBean bean = null;
  try {
   bean = (TestBean) super.clone();
  } catch (CloneNotSupportedException e) {
   e.printStackTrace();
  }
  return bean;
 }

2 实现一个普普通通的RecyclerView.Adapter。

public class DiffAdapter extends RecyclerView.Adapter<DiffAdapter.DiffVH> {
 private final static String TAG = "zxt";
 private List<TestBean> mDatas;
 private Context mContext;
 private LayoutInflater mInflater;

 public DiffAdapter(Context mContext,List<TestBean> mDatas) {
  this.mContext = mContext;
  this.mDatas = mDatas;
  mInflater = LayoutInflater.from(mContext);
 }

 public void setDatas(List<TestBean> mDatas) {
  this.mDatas = mDatas;
 }

 @Override
 public DiffVH onCreateViewHolder(ViewGroup parent,int viewType) {
  return new DiffVH(mInflater.inflate(R.layout.item_diff,parent,false));
 }

 @Override
 public void onBindViewHolder(final DiffVH holder,final int position) {
  TestBean bean = mDatas.get(position);
  holder.tv1.setText(bean.getName());
  holder.tv2.setText(bean.getDesc());
  holder.iv.setImageResource(bean.getPic());
 }

 @Override
 public int getItemCount() {
  return mDatas != null ? mDatas.size() : 0;
 }

 class DiffVH extends RecyclerView.ViewHolder {
  TextView tv1,tv2;
  ImageView iv;

  public DiffVH(View itemView) {
   super(itemView);
   tv1 = (TextView) itemView.findViewById(R.id.tv1);
   tv2 = (TextView) itemView.findViewById(R.id.tv2);
   iv = (ImageView) itemView.findViewById(R.id.iv);
  }
 }
}

3 Activity代码:

public class MainActivity extends AppCompatActivity {
 private List<TestBean> mDatas;
 private RecyclerView mRv;
 private DiffAdapter mAdapter;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  initData();
  mRv = (RecyclerView) findViewById(R.id.rv);
  mRv.setLayoutManager(new LinearLayoutManager(this));
  mAdapter = new DiffAdapter(this,mDatas);
  mRv.setAdapter(mAdapter);
 }

 private void initData() {
  mDatas = new ArrayList<>();
  mDatas.add(new TestBean("张旭童1","Android",R.drawable.pic1));
  mDatas.add(new TestBean("张旭童2","Java",R.drawable.pic2));
  mDatas.add(new TestBean("张旭童3","背锅",R.drawable.pic3));
  mDatas.add(new TestBean("张旭童4","手撕产品",R.drawable.pic4));
  mDatas.add(new TestBean("张旭童5","手撕测试",R.drawable.pic5));
 }

 /**
  * 模拟刷新操作
  *
  * @param view
  */
 public void onRefresh(View view) {
  try {
   List<TestBean> newDatas = new ArrayList<>();
   for (TestBean bean : mDatas) {
    newDatas.add(bean.clone());//clone一遍旧数据 ,模拟刷新操作
   }
   newDatas.add(new TestBean("赵子龙","帅",R.drawable.pic6));//模拟新增数据
   newDatas.get(0).setDesc("Android+");
   newDatas.get(0).setPic(R.drawable.pic7);//模拟修改数据
   TestBean testBean = newDatas.get(1);//模拟数据位移
   newDatas.remove(testBean);
   newDatas.add(testBean);
   //别忘了将新数据给Adapter
   mDatas = newDatas;
   mAdapter.setDatas(mDatas);
   mAdapter.notifyDataSetChanged();//以前我们大多数情况下只能这样
  } catch (CloneNotSupportedException e) {
   e.printStackTrace();
  }
 }

}

很简单,只不过在构建新数据源newDatas时,是遍历老数据源mDatas,调用每个data的clone()方法,确保新老数据源虽然数据一致,但是内存地址(指针不一致),这样在后面修改newDatas里的值时,不会牵连mDatas里的值被一起改了。

4 activity_main.xml 删掉了一些宽高代码,就是一个RecyclerView和一个Button用于模拟刷新。:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
>

 <android.support.v7.widget.RecyclerView
  android:id="@+id/rv" />

 <Button
  android:id="@+id/btnRefresh"
  android:layout_alignParentRight="true"
  android:onClick="onRefresh"
  android:text="模拟刷新" />
</RelativeLayout>

以上是一个普通青年很容易写出的,无脑notifyDataSetChanged()的demo,运行效果如第一节图一。
但是我们都要争做文艺青年,so

下面开始进入正题,简单使用DiffUtil,我们需要且仅需要额外编写一个类。

想成为文艺青年,我们需要实现一个继承自DiffUtil.Callback的类,实现它的四个abstract方法。
虽然这个类叫Callback,但是把它理解成:定义了一些用来比较新老Item是否相等的契约(Contract)、规则(Rule)的类, 更合适。

DiffUtil.Callback抽象类如下:

 public abstract static class Callback {
  public abstract int getOldListSize();//老数据集size

  public abstract int getNewListSize();//新数据集size

  public abstract boolean areItemsTheSame(int oldItemPosition,int newItemPosition);//新老数据集在同一个postion的Item是否是一个对象?(可能内容不同,如果这里返回true,会调用下面的方法)

  public abstract boolean areContentsTheSame(int oldItemPosition,int newItemPosition);//这个方法仅仅是上面方法返回ture才会调用,我的理解是只有notifyItemRangeChanged()才会调用,判断item的内容是否有变化

  //该方法在DiffUtil高级用法中用到 ,暂且不提
  @Nullable
  public Object getChangePayload(int oldItemPosition,int newItemPosition) {
   return null;
  }
 }

  本Demo如下实现DiffUtil.Callback,核心方法配有中英双语注释(说人话就是,翻译了官方的英文注释,方便大家更好理解)。

/**
 * 介绍:核心类 用来判断 新旧Item是否相等
 * 作者:zhangxutong
 * 邮箱:zhangxutong@imcoming.com
 * 时间: 2016/9/12.
 */

public class DiffCallBack extends DiffUtil.Callback {
 private List<TestBean> mOldDatas,mNewDatas;//看名字

 public DiffCallBack(List<TestBean> mOldDatas,List<TestBean> mNewDatas) {
  this.mOldDatas = mOldDatas;
  this.mNewDatas = mNewDatas;
 }

 //老数据集size
 @Override
 public int getOldListSize() {
  return mOldDatas != null ? mOldDatas.size() : 0;
 }

 //新数据集size
 @Override
 public int getNewListSize() {
  return mNewDatas != null ? mNewDatas.size() : 0;
 }

 /**
  * Called by the DiffUtil to decide whether two object represent the same Item.
  * 被DiffUtil调用,用来判断 两个对象是否是相同的Item。
  * For example,if your items have unique ids,this method should check their id equality.
  * 例如,如果你的Item有唯一的id字段,这个方法就 判断id是否相等。
  * 本例判断name字段是否一致
  *
  * @param oldItemPosition The position of the item in the old list
  * @param newItemPosition The position of the item in the new list
  * @return True if the two items represent the same object or false if they are different.
  */
 @Override
 public boolean areItemsTheSame(int oldItemPosition,int newItemPosition) {
  return mOldDatas.get(oldItemPosition).getName().equals(mNewDatas.get(newItemPosition).getName());
 }

 /**
  * Called by the DiffUtil when it wants to check whether two items have the same data.
  * 被DiffUtil调用,用来检查 两个item是否含有相同的数据
  * DiffUtil uses this information to detect if the contents of an item has changed.
  * DiffUtil用返回的信息(true false)来检测当前item的内容是否发生了变化
  * DiffUtil uses this method to check equality instead of {@link Object#equals(Object)}
  * DiffUtil 用这个方法替代equals方法去检查是否相等。
  * so that you can change its behavior depending on your UI.
  * 所以你可以根据你的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.
  * 例如,如果你用RecyclerView.Adapter 配合DiffUtil使用,你需要返回Item的视觉表现是否相同。
  * This method is called only if {@link #areItemsTheSame(int,int)} returns
  * {@code true} for these items.
  * 这个方法仅仅在areItemsTheSame()返回true时,才调用。
  * @param oldItemPosition The position of the item in the old list
  * @param newItemPosition The position of the item in the new list which replaces the
  *      oldItem
  * @return True if the contents of the items are the same or false if they are different.
  */
 @Override
 public boolean areContentsTheSame(int oldItemPosition,int newItemPosition) {
  TestBean beanOld = mOldDatas.get(oldItemPosition);
  TestBean beanNew = mNewDatas.get(newItemPosition);
  if (!beanOld.getDesc().equals(beanNew.getDesc())) {
   return false;//如果有内容不同,就返回false
  }
  if (beanOld.getPic() != beanNew.getPic()) {
   return false;//如果有内容不同,就返回false
  }
  return true; //默认两个data内容是相同的
 }

注释张写了这么详细的注释+简单的代码,相信一眼可懂。

然后在使用时,注释掉你以前写的notifyDatasetChanged()方法吧,替换成以下代码:

//文艺青年新宠
//利用DiffUtil.calculateDiff()方法,传入一个规则DiffUtil.Callback对象,和是否检测移动item的 boolean变量,得到DiffUtil.DiffResult 的对象

DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallBack(mDatas,true);

//利用DiffUtil.DiffResult对象的dispatchUpdatesTo()方法,传入RecyclerView的Adapter,轻松成为文艺青年

diffResult.dispatchUpdatesTo(mAdapter);

//别忘了将新数据给Adapter
mDatas = newDatas;
mAdapter.setDatas(mDatas);

讲解:

步骤一

在将newDatas 设置给Adapter之前,先调用DiffUtil.calculateDiff()方法,计算出新老数据集转化的最小更新集,就是

DiffUtil.DiffResult对象。
DiffUtil.calculateDiff()方法定义如下:
第一个参数是DiffUtil.Callback对象,
第二个参数代表是否检测Item的移动,改为false算法效率更高,按需设置,我们这里是true。

public static DiffResult calculateDiff(Callback cb,boolean detectMoves)

步骤二

然后利用DiffUtil.DiffResult对象的dispatchUpdatesTo()方法,传入RecyclerView的Adapter,替代普通青年才用的mAdapter.notifyDataSetChanged()方法。

查看源码可知,该方法内部,就是根据情况调用了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,payload);
    }
   });
  }

小结:

所以说,DiffUtil不仅仅只能和RecyclerView配合,我们也可以自己实现ListUpdateCallback接口的四个方法去做一些事情。(我暂时不负责任随便一项想,想到可以配合自己项目里的九宫格控件?或者优化我上篇文章写的NestFullListView?小安利,见 ListView、RecyclerView、ScrollView里嵌套ListView 相对优雅的解决方案:http://blog.csdn.net/zxt0601/article/details/52494665)

至此,我们已进化成文艺青年,运行效果和第一节图二基本一致,

唯一不同的是此时adapter.notifyItemRangeChanged()会有Item白光一闪的更新动画 (本文Demo的postion为0的item)。 这个Item一闪的动画有人喜欢有人恨,不过都不重要了,

因为当我们学会了第三节的DiffUtil搞基用法,你爱不爱这个ItemChange动画,它都将随风而去。(不知道是不是官方bug)
效果就是第一节的图二,我们的item0其实图片和文字都变化了,但是这个改变并没有伴随任何动画。

让我们迈向 文艺青年中的文艺青年 之路。

三 DiffUtil的高级用法

理论:

高级用法只涉及到两个方法,
我们需要分别实现DiffUtil.Callback的
public Object getChangePayload(int oldItemPosition,int newItemPosition)方法,
返回的Object就是表示Item改变了哪些内容。

再配合RecyclerView.Adapter的
public void onBindViewHolder(VH holder,List<Object> payloads)方法,
完成定向刷新。(成为文青中的文青,文青青。)
敲黑板,这是一个新方法,注意它有三个参数,前两个我们熟,第三个参数就包含了我们在getChangePayload()返回的Object。

好吧,那我们就先看看这个方法是何方神圣:

在v7-24.2.0的源码里,它长这个样子:

 /**
   * Called by RecyclerView to display the data at the specified position. This method
   * should update the contents of the {@link ViewHolder#itemView} to reflect the item at
   * the given position.
   * <p>
   * Note that unlike {@link android.widget.ListView},RecyclerView will not call this method
   * again if the position of the item changes in the data set unless the item itself is
   * invalidated or the new position cannot be determined. For this reason,you should only
   * use the <code>position</code> parameter while acquiring the related data item inside
   * this method and should not keep a copy of it. If you need the position of an item later
   * on (e.g. in a click listener),use {@link ViewHolder#getAdapterPosition()} which will
   * have the updated adapter position.
   * <p>
   * Partial bind vs full bind:
   * <p>
   * The payloads parameter is a merge list from {@link #notifyItemChanged(int,Object)} or
   * {@link #notifyItemRangeChanged(int,int,Object)}. If the payloads list is not empty,* the ViewHolder is currently bound to old data and Adapter may run an efficient partial
   * update using the payload info. If the payload is empty,Adapter must run a full bind.
   * Adapter should not assume that the payload passed in notify methods will be received by
   * onBindViewHolder(). For example when the view is not attached to the screen,the
   * payload in notifyItemChange() will be simply dropped.
   *
   * @param holder The ViewHolder which should be updated to represent the contents of the
   *    item at the given position in the data set.
   * @param position The position of the item within the adapter‘s data set.
   * @param payloads A non-null list of merged payloads. Can be empty list if requires full
   *     update.
   */
  public void onBindViewHolder(VH holder,List<Object> payloads) {
   onBindViewHolder(holder,position);
  }

原来它内部就仅仅调用了两个参数的onBindViewHolder(holder,position) ,(题外话,哎哟喂,我的NestFullListView 的Adapter也有几分神似这种写法,看来我离Google大神又近了一步)

看到这我才明白,其实onBind的入口,就是这个方法,它才是和onCreateViewHolder对应的方法,
源码往下翻几行可以看到有个public final void bindViewHolder(VH holder,int position),它内部调用了三参的onBindViewHolder。
关于RecyclerView.Adapter 也不是三言两句说的清楚的。(其实我只掌握到这里)
好了不再跑题,回到我们的三参数的onBindViewHolder(VH holder,List<Object> payloads),这个方法头部有一大堆英文注释,我一直觉得阅读这些英文注释对理解方法很有用处,于是我翻译了一下,

翻译:

由RecyclerView调用 用来在在指定的位置显示数据。
这个方法应该更新ViewHolder里的ItemView的内容,以反映在给定的位置 Item(的变化)。
请注意,不像ListView,如果给定位置的item的数据集变化了,RecyclerView不会再次调用这个方法,除非item本身失效了(invalidated ) 或者新的位置不能确定。
出于这个原因,在这个方法里,你应该只使用 postion参数 去获取相关的数据item,而且不应该去保持 这个数据item的副本。
如果你稍后需要这个item的position,例如设置clickListener。应该使用 ViewHolder.getAdapterPosition(),它能提供 更新后的位置。
(二笔的我看到这里发现 这是在讲解两参的onbindViewHolder方法
下面是这个三参方法的独特部分:)
**部分(partial)绑定**vs完整(full)绑定
payloads 参数 是一个从(notifyItemChanged(int,Object)或notifyItemRangeChanged(int,Object))里得到的合并list。
如果payloads list 不为空,那么当前绑定了旧数据的ViewHolder 和Adapter, 可以使用 payload的数据进行一次 高效的部分更新。
如果payload 是空的,Adapter必须进行一次完整绑定(调用两参方法)。
Adapter不应该假定(想当然的认为) 在那些notifyxxxx通知方法传递过来的payload, 一定会在 onBindViewHolder()方法里收到。(这一句翻译不好 QAQ 看举例就好)
举例来说,当View没有attached 在屏幕上时,这个来自notifyItemChange()的payload 就简单的丢掉好了。
payloads对象不会为null,但是它可能是空(empty),这时候需要完整绑定(所以我们在方法里只要判断isEmpty就好,不用重复判空)。
作者语:这方法是一个高效的方法。 我是个低效的翻译者,我看了40+分钟。才终于明白,重要的部分已经加粗显示。

实战:

说了这么多话,其实用起来超级简单:
先看如何使用getChangePayload()方法,又附带了中英双语注释

  

 /**
  * 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.
  * 
  * 当{@link #areItemsTheSame(int,int)} 返回true,且{@link #areContentsTheSame(int,int)} 返回false时,DiffUtils会回调此方法,
  * 去得到这个Item(有哪些)改变的payload。
  * 
  * 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.
  * 
  * 例如,如果你用RecyclerView配合DiffUtils,你可以返回 这个Item改变的那些字段,
  * {@link android.support.v7.widget.RecyclerView.ItemAnimator ItemAnimator} 可以用那些信息去执行正确的动画
  * 
  * Default implementation returns {@code null}.  * 默认的实现是返回null
  *
  * @param oldItemPosition The position of the item in the old list
  * @param newItemPosition The position of the item in the new list
  * @return A payload object that represents the change between the two items.
  * 返回 一个 代表着新老item的改变内容的 payload对象,
  */
 @Nullable
 @Override
 public Object getChangePayload(int oldItemPosition,int newItemPosition) {
  //实现这个方法 就能成为文艺青年中的文艺青年
  // 定向刷新中的部分更新
  // 效率最高
  //只是没有了ItemChange的白光一闪动画,(反正我也觉得不太重要)
  TestBean oldBean = mOldDatas.get(oldItemPosition);
  TestBean newBean = mNewDatas.get(newItemPosition);

  //这里就不用比较核心字段了,一定相等
  Bundle payload = new Bundle();
  if (!oldBean.getDesc().equals(newBean.getDesc())) {
   payload.putString("KEY_DESC",newBean.getDesc());
  }
  if (oldBean.getPic() != newBean.getPic()) {
   payload.putInt("KEY_PIC",newBean.getPic());
  }

  if (payload.size() == 0)//如果没有变化 就传空
   return null;
  return payload;//
 }

简单的说,这个方法返回一个Object类型的payload,它包含了某个item的变化了的那些内容。
我们这里使用Bundle保存这些变化。

在Adapter里如下重写三参的onBindViewHolder:

 @Override
 public void onBindViewHolder(DiffVH holder,List<Object> payloads) {
  if (payloads.isEmpty()) {
   onBindViewHolder(holder,position);
  } else {
   //文艺青年中的文青
   Bundle payload = (Bundle) payloads.get(0);
   TestBean bean = mDatas.get(position);
   for (String key : payload.keySet()) {
    switch (key) {
     case "KEY_DESC":
      //这里可以用payload里的数据,不过data也是新的 也可以用
      holder.tv2.setText(bean.getDesc());
      break;
     case "KEY_PIC":
      holder.iv.setImageResource(payload.getInt(key));
      break;
     default:
      break;
    }
   }
  }
 }

这里传递过来的payloads是一个List,由注释可知,一定不为null,所以我们判断是否是empty,
如果是empty,就调用两参的函数,进行一次Full Bind。
如果不是empty,就进行partial bind,
通过下标0取出我们在getChangePayload方法里返回的payload,然后遍历payload的key,根据key检索,如果payload里携带有相应的改变,就取出来 然后更新在ItemView上。
(这里,通过mDatas获得的也是最新数据源的数据,所以用payload的数据或者新数据的数据 进行更新都可以)

至此,我们已经掌握了刷新RecyclerView,文艺青年中最文艺的那种写法。

四 在子线程中使用DiffUtil

在DiffUtil的源码头部注释中介绍了DiffUtil的相关信息,
DiffUtil内部采用的Eugene W. Myers's difference 算法,但该算法不能检测移动的item,所以Google在其基础上改进支持检测移动项目,但是检测移动项目,会更耗性能。
在有1000项数据,200处改动时,这个算法的耗时:
打开了移动检测时:平均值:27.07ms,中位数:26.92ms。
关闭了移动检测时:平均值:13.54ms,中位数:13.36ms。
有兴趣可以自行去源码头部阅读注释,对我们比较有用的是其中一段提到,
如果我们的list过大,这个计算出DiffResult的时间还是蛮久的,所以我们应该将获取DiffResult的过程放到子线程中,并在主线程中更新RecyclerView。

这里我采用Handler配合DiffUtil使用:

代码如下:

 private static final int H_CODE_UPDATE = 1;
 private List<TestBean> mNewDatas;//增加一个变量暂存newList
 private Handler mHandler = new Handler() {
  @Override
  public void handleMessage(Message msg) {
   switch (msg.what) {
    case H_CODE_UPDATE:
     //取出Result
     DiffUtil.DiffResult diffResult = (DiffUtil.DiffResult) msg.obj;
     diffResult.dispatchUpdatesTo(mAdapter);
     //别忘了将新数据给Adapter
     mDatas = mNewDatas;
     mAdapter.setDatas(mDatas);
     break;
   }
  }
 };
   new Thread(new Runnable() {
    @Override
    public void run() {
     //放在子线程中计算DiffResult
     DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallBack(mDatas,mNewDatas),true);
     Message message = mHandler.obtainMessage(H_CODE_UPDATE);
     message.obj = diffResult;//obj存放DiffResult
     message.sendToTarget();
    }
   }).start();

就是简单的Handler使用,不再赘述。

五总结和其他

1 其实本文代码量很少,可下载Demo查看,一共就四个类。
但是不知不觉又被我写的这么长,主要涉及到了一些源码的注释的翻译,方便大家更好的理解。

2 DiffUtil很适合下拉刷新这种场景,
更新的效率提高了,而且带动画,而且~还不用你动脑子算了。
不过若是就做个删除 点赞这种,完全不用DiffUtils。自己记好postion,判断一下postion在不在屏幕里,调用那几个定向刷新的方法即可。

3 其实DiffUtil不是只能和RecyclerView.Adapter配合使用,
我们可以自己实现 ListUpdateCallback接口,利用DIffUtil帮我们找到新旧数据集的最小差异集 来做更多的事情。

4 注意 写DEMO的时候,用于比较的新老数据集,不仅ArrayList不同,里面每个data也要不同。 否则changed 无法触发。
实际项目中遇不到,因为新数据往往是网络来的。

5 今天是中秋节的最后一天,我们公司居然就开始上班了!!!气愤之余,我怒码一篇DiffUtil,我都不需要用DiffUtil,也能轻易比较出我们公司和其他公司的差异。QAQ,而且今天状态不佳,居然写了8个小时才完工。本以为这篇文章是可以入选微作文集的,没想到也是蛮长的。没有耐心的其实可以下载DEMO看看,代码量没多少,使用起来还是很轻松的。

github传送门:
https://github.com/mcxtzhang/DiffUtils

以上就是对Android7.0 工具类DiffUtil 的资料整理,后续继续补充相关资料,谢谢大家对本站的支持!

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


AdvserView.java package com.earen.viewflipper; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory;
ImageView的scaleType的属性有好几种,分别是matrix(默认)、center、centerCrop、centerInside、fitCenter、fitEnd、fitStart、fitXY。 |值|说明| |:--:|:--| |center|保持原图的大小,显示在ImageVie
文章浏览阅读8.8k次,点赞9次,收藏20次。本文操作环境:win10/Android studio 3.21.环境配置 在SDK Tools里选择 CMAKE/LLDB/NDK点击OK 安装这些插件. 2.创建CMakeLists.txt文件 在Project 目录下,右键app,点击新建File文件,命名为CMakeLists.txt点击OK,创建完毕! 3.配置文件 在CMa..._link c++ project with gradle
文章浏览阅读1.2w次,点赞15次,收藏69次。实现目的:由mainActivity界面跳转到otherActivity界面1.写好两个layout文件,activity_main.xml和otherxml.xmlactivity_main.xml&lt;?xml version="1.0" encoding="utf-8"?&gt;&lt;RelativeLayout ="http://schemas..._android studio 界面跳转
文章浏览阅读3.8w次。前言:最近在找Android上的全局代理软件来用,然后发现了这两款神作,都是外国的软件,而且都是开源的软件,因此把源码下载了下来,给有需要研究代理这方面的童鞋看看。不得不说,国外的开源精神十分浓,大家相互使用当前基础的开源软件,然后组合成一个更大更强的大开源软件。好吧,废话不多说,下面简单介绍一下这两款开源项目。一、ProxyDroid:ProxyDroid功能比较强大,用到的技术也比较多,源码也_proxydroid
文章浏览阅读2.5w次,点赞17次,收藏6次。创建项目后,运行项目时Gradle Build 窗口却显示错误:程序包R不存在通常情况下是不会出现这个错误的。我是怎么遇到这个错误的呢?第一次创建项目,company Domain我使用的是:aven.com,但是创建过程在卡在了Building 'Calculator' Gradle Project info这个过程中,于是我选择了“Cancel”第二次创建项目,我还是使用相同的项目名称和项目路_r不存在
文章浏览阅读8.9w次,点赞4次,收藏43次。前言:在Android上使用系统自带的代理,限制灰常大,仅支持系统自带的浏览器。这样像QQ、飞信、微博等这些单独的App都不能使用系统的代理。如何让所有软件都能正常代理呢?ProxyDroid这个软件能帮你解决!使用方法及步骤如下:一、推荐从Google Play下载ProxyDroid,目前最新版本是v2.6.6。二、对ProxyDroid进行配置(基本配置:) (1) Auto S_proxydroid使用教程
文章浏览阅读1.1w次,点赞4次,收藏17次。Android Studio提供了一个很实用的工具Android设备监视器(Android device monitor),该监视器中最常用的一个工具就是DDMS(Dalvik Debug Monitor Service),是 Android 开发环境中的Dalvik虚拟机调试监控服务。可以进行的操作有:为测试设备截屏,查看特定进程中正在运行的线程以及堆栈信息、Logcat、广播状态信息、模拟电话_安卓摄像头调试工具
文章浏览阅读2.1k次。初学Android游戏开发的朋友,往往会显得有些无所适从,他们常常不知道该从何处入手,每当遇到自己无法解决的难题时,又往往会一边羡慕于 iPhone下有诸如Cocos2d-iphone之类的免费游戏引擎可供使用,一边自暴自弃的抱怨Android平台游戏开发难度太高,又连个像样的游 戏引擎也没有,甚至误以为使用Java语言开发游戏是一件费力不讨好且没有出路的事情。事实上,这种想法完全是没有必_有素材的游戏引擎
文章浏览阅读3.2k次,点赞2次,收藏2次。2014年12月从csdn专家福利获得的一本书《Android游戏开发技术实战详解》,尘封了一年多的时间,今天才翻开来看。我认识中的Android,提到Android最先浮现在我脑海中的是那可爱的机器人图标:这个Logo是由Ascender公司设计的,诞生于2010年,其设计灵感源于男女厕所门上的图形符号(真的是灵感无处不在),于是布洛克绘制了一个简单的机器人,它的躯干就像锡罐的形状,头上还有两根_智能手机的特点有哪些?
文章浏览阅读8.1k次,点赞9次,收藏11次。首先,Android是不是真的找工作越来越难呢?这个可能是大家最关心的。这个受大的经济环境以及行业发展前景的影响,同时也和个人因素有关。2016-08-26近期一方面是所在的公司招聘Java开发人员很难招到合适的,投简历的人很少;而另一方面,经常听身边的人说Android、iOS方面找工作不好找,特别是没什么经验的,经验比较少的!说是不好找,但在我家所在的吉林省省会长春,会Unity3D+Maya_android 开发和asp.net哪个好 site:blog.csdn.net
文章浏览阅读6.1k次。在上篇“走进Android开发的世界,HelloWorld”,我们创建了一个Android 项目 HelloWorld,并演示了如何通过USB连接手机查看运行效果;而如果没有手机或没有对应型号的手机,又想做对应型号(屏幕尺寸、Android系统版本)的适配,应该怎么办呢?这时Android模拟器就派上用场了。Android模拟器Android SDK自带一个移动模拟器。它是一个可以运行在你电脑上的_安卓移动开发软件怎样预览
文章浏览阅读8.9k次。Google IO 2017 上宣布,将Kotlin语言作为安卓开发的官方语言。Kotlin由JetBrains公司开发,与Java 100%互通,并具备诸多Java尚不支持的新特性。谷歌称还将与JetBrains公司合作,为Kotlin设立一个非盈利基金会。Kotlin 是一个基于 JVM 的静态类型编程语言,Kotlin可以编译成Java字节码,也可以编译成JavaScript,方便在没有JV_kotlin为什么被嫌弃
文章浏览阅读9.6w次,点赞17次,收藏35次。有些情况下,不方便使用断点的方式来调试,而是希望在控制台打印输出日志,使用过Eclipse的同学都知道Java可以使用 System.out.println(""); 来在控制台打印输出日志,但是在android studio中却是不行的,还是有差别的,那应该用什么呢?android.util.Log在调试代码的时候我们需要查看调试信息,那我们就需要用Android Log类。android.ut_andirod.studio 为什么不在控制台打印输出
文章浏览阅读8.2k次,点赞2次,收藏8次。在上篇“走进Android开发的世界,HelloWorld”,我们创建了一个Android 项目 HelloWorld,并演示了如何通过USB连接手机查看运行效果;这里讲一下如何为应用添加一个按钮,并为按钮添加Click单击事件处理程序,显示/隐藏另一个按钮。添加按钮在HelloWorld项目的基础上,打开界面布局文件:activity_main.xml切换到Design(设计)模式;在组件But_activity_main.xml按钮隐藏
文章浏览阅读2.9k次,点赞3次,收藏9次。android 开发工具主流的还是Android Studio,当然也有很多人喜欢用Eclipse,也有人喜欢用IntelliJ IDEA ;还有Xamarin这种只需要编写一次代码,可以编译多种平台可运行的强大工具。但是它又真的强大吗?就我看来没有,身边很多人还是在用Android Studio、XCode开发应用,没见谁在用Xamarin之类的工具。系统要求WindowsMicrosoft®_android开发下载安装
文章浏览阅读4.2k次,点赞7次,收藏26次。你知道Hello World程序的由来吗?对于大多数编程语言的学习来说,真正入门的一课就是 Hello World!会而不难,难而不会。虽然很多人写过关于Android开发Hello World的文章,但随着时间的推移,开发工具、技术的进步,可能有些已经过时了。我就记录一下当下我所经历的第一个Android APP HelloWorld。一、准备1、开发环境参考:Android Studio 下载_android helloworld textview 句柄获取
这篇“android轻量级无侵入式管理数据库自动升级组件怎么实现”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定...
今天小编给大家分享一下Android实现自定义圆形进度条的常用方法有哪些的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文...
这篇文章主要讲解了“Android如何解决字符对齐问题”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Android...