一个新的工具类诞生了 DiffUtil 今天初学了一番
简介
DiffUtil是support-v7:24.2.0中的新工具类,它用来比较两个数据集,寻找出旧数据集-》新数据集的最小变化量。
-
区别
(1) 它最大的用处就是在RecyclerView刷新时,不再使用mAdapter.notifyDataSetChanged()。
mAdapter.notifyDataSetChanged()有两个缺点:
- 不会触发RecyclerView的动画(删除、新增、位移、change动画)
- 性能较低,毕竟是无脑的刷新了一遍整个RecyclerView , 极端情况下:新老数据集一模一样,效率是最低的。
(2) 它会自动计算新老数据集的差异,并根据差异情况,自动调用以下四个方法
- adapter.notifyItemRangeInserted(position, count);
- adapter.notifyItemRangeRemoved(position, count);
adapter.notifyItemMoved(fromPosition, toPosition);
adapter.notifyItemRangeChanged(position, count, payload);
学习点
- DiffUtil的简单用法,实现刷新时的“增量更新”效果
- DiffUtil的高级用法,在某项Item只有内容(data)变化,位置(position) 未变化时,完成部分更新(官方称之为Partial bind,部分绑定)。
- 在子线程中计算DiffResult,在主线程中刷新RecyclerView。
- DiffUtil部分类、方法 官方注释的汉化
简单用法
- activity 代码
将获取DiffResult的过程放到子线程中,并在主线程中更新RecyclerView。
这里我采用Handler配合DiffUtil使用
public class MainActivity extends AppCompatActivity {
private List 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", "PHP", R.drawable.pic3));
mDatas.add(new TestBean("柠檬4", "C", R.drawable.pic4));
mDatas.add(new TestBean("柠檬5", "IOS", R.drawable.pic5));
}
/**
* 模拟刷新操作
*
* @param view
*/
public void onRefresh(View view) {
try {
mNewDatas = new ArrayList<>();
for (TestBean bean : mDatas) {
mNewDatas.add(bean.clone());//clone一遍旧数据 ,模拟刷新操作
}
mNewDatas.add(new TestBean("SIRAI", "DiffUtil", R.drawable.pic2));//模拟新增数据
mNewDatas.get(0).setDesc("refresh+");
mNewDatas.get(0).setPic(R.drawable.pic1);//模拟修改数据
TestBean testBean = mNewDatas.get(1);//模拟数据位移
mNewDatas.remove(testBean);
mNewDatas.add(testBean);
//新宠
//利用DiffUtil.calculateDiff()方法,传入一个规则DiffUtil.Callback对象,和是否检测移动item的 boolean变量,得到DiffUtil.DiffResult 的对象
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();
//mAdapter.notifyDataSetChanged();//以前普通青年的我们只能这样,现在我们是文艺青年了,有新宠了
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
private static final int H_CODE_UPDATE = 1;
private List 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;
//利用DiffUtil.DiffResult对象的dispatchUpdatesTo()方法,传入RecyclerView的Adapter,轻松成为文艺青年
diffResult.dispatchUpdatesTo(mAdapter);
//别忘了将新数据给Adapter
mDatas = mNewDatas;
mAdapter.setDatas(mDatas);
break;
}
}
};
}
- DiffCallBack
实现一个继承自DiffUtil.Callback的类,实现它的四个abstract方法。
public class DiffCallBack extends DiffUtil.Callback { private List
mOldDatas, mNewDatas;//看名字 public DiffCallBack(List mOldDatas, List 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内容是相同的 } /** * 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;// } }
- DiffAdapter
亮点:onBindViewHolder(DiffVH holder, int position, List