本文主要介绍Android系统中提供的工具类DiffUtil,DiffUtil的主是用与RecyclerView的局部更新,从而提高页面刷新效率。
本文基于最新的v7-27.1.1版本的RecyclerView做介绍,因为这个兼容包里面新增了一个ListAdapter,一并做介绍。
DiffUtil是在安卓7.0上引入的工具类,相对于“传统”的nofityDataChange()方式:
先上demo,看一下DiffUtil要怎么用
public static final class MyDiffUtilCallback extends DiffUtil.Callback {
private List mOldList;
private List mNewList;
public MyDiffUtilCallback setDates(List oldList, List newList) {
mOldList = oldList;
mNewList = newList;
return this;
}
@Override
public int getOldListSize() {
return mOldList.size();
}
@Override
public int getNewListSize() {
return mNewList.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return mOldList.get(oldItemPosition).id == mNewList.get(newItemPosition).id;
// return true;
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return mOldList.get(oldItemPosition).content.equals(mNewList.get(newItemPosition).content);
}
}
...
public void onNewData(List newData) {
//数据源太大时计算费时,放入子线程
//最新版本兼容包已出ListAdapter,自带子线程执行
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(mMyDiffUtilCallback.setDates(mBeans, newData));
diffResult.dispatchUpdatesTo(MyAdapter.this);
mBeans.clear();
mBeans.addAll(newData);
}
上面的代码很简单:
我们可以看到DiffUtil.Callback主要有4个抽象方法需要实现,看一下这4个方法的具体作用。
public abstract int getOldListSize();
public abstract int getNewListSize();
上面2个方法主要获取新老数据的长度。
//返回值表示新数据传入时这两个位置的数据是否时同一个条目
public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition);
//返回值表示新老位置的数据内容是否相同,这个方法在areItemsTheSame()返回true时生效
public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition);
上面这两个方法就是区分两个数据bean是否相同。
敲黑板,这里有个注意点:
当areItemsTheSame返回为false时,不管areContentsTheSame是否为true,adapter中的条目都会更新
DiffUtil.Callback中还有另外一个可以复写的方法
@Nullable
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
return null;
}
当areItemsTheSame()返回true,同时areContentsTheSame()返回false时,通过这个方法可以用来返回具体的数据差异。
我们来看一下用法
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return mOldList.get(oldItemPosition).id == mNewList.get(newItemPosition).id;
return true;
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return false;
}
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
Bean oldBean = mOldList.get(oldItemPosition);
Bean newBean = mNewList.get(newItemPosition);
Bundle bundle = new Bundle();
if (!oldBean.content.equals(newBean.content)) {
bundle.putString("content", newBean.content);
}
if (bundle.size() > 0) {
return bundle;
}
return null;
}
然后在Recycler的Adapter中我们通过onBindViewHolder()的另一个重载方法获取到之前设置的数据进行局部刷新。
@Override
public void onBindViewHolder(MyAdapter.ViewHolder holder, int position, @NonNull List payloads) {
if (payloads.isEmpty()) {
super.onBindViewHolder(holder, position, payloads);
//内部其实就是调用的全部刷新的方法
//onBindViewHolder(holder, position);
} else {
Bundle bundle = (Bundle) payloads.get(0);
for (String key : bundle.keySet()) {
if (key.equals("content")) {
holder.contentView.setText(bundle.getString("content"));
}
}
}
}
好了,这里就是DiffUtil的用法了,实现其中的Callback用户比较数据的差异,当来新数据时,调用其中的calculateDiff()静态方法计算数据差异即可。如果你想实现RecyclerView的局部刷新,那可以实现getChangePayload。
当数据量不大时,我们可以在UI线程中直接更新数据,但是当数据量大时这就比较尴尬了,我们需要自己放在子线程操作,然后再回UI线程更新页面。
在7.0上引入DiffUtil之后,现在又在最新的v7的27.1.1兼容包中加入的官方的支持,就是ListAdapter。
public abstract class ListAdapter<T, VH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<VH> {
private final AsyncListDiffer mHelper;
@SuppressWarnings("unused")
protected ListAdapter(@NonNull DiffUtil.ItemCallback diffCallback) {
mHelper = new AsyncListDiffer<>(new AdapterListUpdateCallback(this),
new AsyncDifferConfig.Builder<>(diffCallback).build());
}
@SuppressWarnings("unused")
protected ListAdapter(@NonNull AsyncDifferConfig config) {
mHelper = new AsyncListDiffer<>(new AdapterListUpdateCallback(this), config);
}
/**
* Submits a new list to be diffed, and displayed.
*
* If a list is already being displayed, a diff will be computed on a background thread, which
* will dispatch Adapter.notifyItem events on the main thread.
*
* @param list The new list to be displayed.
*/
@SuppressWarnings("WeakerAccess")
public void submitList(List list) {
mHelper.submitList(list);
}
@SuppressWarnings("unused")
protected T getItem(int position) {
return mHelper.getCurrentList().get(position);
}
@Override
public int getItemCount() {
return mHelper.getCurrentList().size();
}
}
ListAdapter用法就跟普通的RecyclerView并无太大差别。