DiffUtil的作用是比较两个数据列表并能计算出一系列将旧数据表转换成新数据表的操作。它不再是简单数据更新,而是根据数据的变化去调用RecyclerView不同的刷新方法,将RecyclerView的个性化体现的淋漓尽致
在24.2.0支持包更新中,Google在RecyclerView
的支持包中新增了DiffUtil
类用来进行数据刷新,因为以前在使用RecyclerView
的时候遇到过坑,尤其是下拉刷新的时候有时候会报错,因此对这个类报很大的期待。但是查看源码后不禁再次对Google的程序员深深的佩服,原理大分部都看懂了,但是算法不是我强项,知道他怎么对比的,但是速度优化方面就没有具体看了
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。
当数据集较大时,你应该在后台线程计算数据集的更新。
1、数据对比
使用DiffUtil
首先要继承一个抽象类DiffUtil.Callback
,DiffUtil
是通过这个类来识别判断新旧2个集合有何不同的
public class ItemDiffCallBack extends DiffUtil.Callback {
private List<Item> mOldList;
private List<Item> mNewList;
private final String TAG = getClass().getSimpleName();
public ItemDiffCallBack(List<Item> oldList, List<Item> newList) {
this.mOldList = oldList;
this.mNewList = newList;
}
@Override
public int getOldListSize() {
return mOldList == null ? 0 : mOldList.size();
}
@Override
public int getNewListSize() {
return mNewList == null ? 0 : mNewList.size();
}
//这个是用来判断是否是一个对象的
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return mOldList.get(oldItemPosition).id == mNewList.get(newItemPosition).id;
}
//这个是用来判断相同对象的内容是否相同
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
String oldContent = mOldList.get(oldItemPosition).content();
String newContent = mNewList.get(newItemPosition).content();
Log.i(TAG, "oldContent:" + oldContent + " newContent:" + newContent);
return TextUtils.equals(oldContent ,newContent );
}
//找出其中的不同
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
Item oldItem = mOldList.get(oldItemPosition);
Item newItem = mNewList.get(newItemPosition);
Bundle diffBundle = new Bundle();
if (!TextUtils.equals(oldItem.content(), newItem.content())) {
diffBundle.putString("content", newItem.content());
}
return diffBundle;
}
}
代码比较简单,主要的地方我也做了注释,简单的解释一下,这个东西并不一定要2个对象完全相同,只需要你界面上显示的数据相同就可以了,毕竟这个只是用来刷新界面的,你对比的数据越少,速度就会越快。
2、Adapter刷新以及差异化更新
通过上面的类,DiffUtil
可以得到一个变更集合DiffResult
,DiffResult
会根据数据的增删改去调用Adapter
的notifyItemRangeInserted
、notifyItemRangeRemoved
、notifyItemMoved
、notifyItemRangeChanged
,Adapter中要重写onBindViewHolder
3个参数的方法
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List<Object> payloads) {
//判断数据更改是否为空,说明是新增的,直接去绑定数据
if (payloads == null || payloads.isEmpty()) {
onBindViewHolder(holder,position);
Log.i(TAG, "position:" + position + " payloads is empty");
return;
}
if (!(holder instanceof ItemViewHolder)) {
return;
}
//如果不为空,说明有部分数据发生了更改,那么只要根据数据去更新变更的UI即可
ItemViewHolder viewHolder = (ItemViewHolder) holder;
Bundle bundle = (Bundle) payloads.get(0);
String content = bundle.getString("content");
viewHolder.tvPosition.setText(content);
Log.i(TAG, "position:" + position + " payloads is not empty");
}
我看了很多博客(虽然大部分都是转载的),这个方法中他们都首先去调用了绑定方法,然后才去判断payloads
是否为空,这样的话就失去了DiffUtil
一大特性,也牺牲了一部分性能
这个方法其实还有其他的用处,例如你这个viewHolder里面有3个控件有动画,但是只有一个数据发生了变化,如果整体notify,那么3个动画都会去播放,在这里就可以只播放一个动画,让用户更清楚的知道是哪个数据发生了改变,差异化在这个地方体现的淋漓尽致
3、使用
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new ItemDiffCallBack(oldItemList, mItemList));
diffResult.dispatchUpdatesTo(mItemAdapter);
使用很简单就不用多说了