一.DiffUtil工具类简介
1.说明
DiffUtil工具类是RecyclerView包中提供的一个工具类。
2.源码解释
英文
DiffUtil is a utility class that calculates the difference
between two lists and outputs a list of update operations that converts the first list into the second one.
翻译
DiffUtil是一个实用程序类,用于计算两个列表之间的差异,并输出将第一个列表转换为第二个列表的更新操作列表。
3.源码时间耗时说明
<li>100 items and 10 modifications: avg: 0.39 ms, median: 0.35 ms
<li>100 items and 100 modifications: 3.82 ms, median: 3.75 ms
<li>100 items and 100 modifications without moves: 2.09 ms, median: 2.06 ms
<li>1000 items and 50 modifications: avg: 4.67 ms, median: 4.59 ms
<li>1000 items and 50 modifications without moves: avg: 3.59 ms, median: 3.50 ms
<li>1000 items and 200 modifications: 27.07 ms, median: 26.92 ms
<li>1000 items and 200 modifications without moves: 13.54 ms, median: 13.36 ms
可以看出DiffUtil工具类的性能还是非常不错的。
二.DiffUtil工具类使用
Activity
public class RecyclerViewUpdateActivity extends AppCompatActivity {
private SwipeRefreshLayout mSwipeRefreshLayout;
private RecyclerView mRecyclerView;
private RecycleViewUpdateAdapter mRecycleViewAdapter;
private List<RecyclerViewBean> mList;
private int mCount = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recyclerview);
initView();
}
/**
* 初始化各种View
*/
private void initView() {
mSwipeRefreshLayout = findViewById(R.id.activity_recyclerview_srl);
initSwipeRefreshLayout();
mRecyclerView = findViewById(R.id.activity_recyclerview_recyclerView);
mList = getList();
//1.设置LinearLayoutManager ListView
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
//2.设置固定大小
mRecyclerView.setHasFixedSize(true);
//3.设置Adapter
mRecycleViewAdapter = new RecycleViewUpdateAdapter(this, mList);
mRecyclerView.setAdapter(mRecycleViewAdapter);
//4.RecyclerView点击事件
onRecyclerViewClick();
}
/**
* 初始化SwipeRefreshLayout
*/
private void initSwipeRefreshLayout() {
//设置进度View的组合颜色,在手指上下滑时使用第一个颜色,在刷新中,会一个个颜色进行切换
mSwipeRefreshLayout.setColorSchemeColors(Color.parseColor("#6200EE"), Color.parseColor("#3700B3"), Color.parseColor("#03DAC5"), Color.YELLOW, Color.BLUE);
//下拉刷新
mSwipeRefreshLayout.setOnRefreshListener(() -> {
//模拟6秒刷新时间
Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(() -> {
RecyclerViewUpdateActivity.this.runOnUiThread(() -> {
List<RecyclerViewBean> oldList = mRecycleViewAdapter.getData();
// mList = getUpdateList();//数据变化
mList = getList();//数据不变
mRecycleViewAdapter.updateData(oldList, mList);//DiffUtil差量刷新 同步
});
mSwipeRefreshLayout.setRefreshing(false);//结束刷新
}, 6000);
});
}
/**
* RecyclerView点击事件
*/
private void onRecyclerViewClick() {
mRecycleViewAdapter.setRecyclerViewItemClick(new IRecyclerViewItemClick() {
@Override
public void onItemClick(View view, int position) {
Log.d("DiffUtilCallBack", "RecyclerView点击 position----:" + position);
Log.d("DiffUtilCallBack", "RecyclerView点击 title----:" + mList.get(position).getTitle());
}
@Override
public void onItemLongClick(View view, int position) {
Log.d("DiffUtilCallBack", "RecyclerView长按 position----:" + position);
Log.d("DiffUtilCallBack", "RecyclerView长按 title----:" + mList.get(position).getTitle());
}
});
}
/**
* 初始化 默认集合数据
*/
private List<RecyclerViewBean> getList() {
List<RecyclerViewBean> list = new ArrayList<>();
RecyclerViewBean listViewBean1 = new RecyclerViewBean();
listViewBean1.setAva("http://sss");
listViewBean1.setTitle("张三");
RecyclerViewBean listViewBean2 = new RecyclerViewBean();
listViewBean2.setAva("http://sss");
listViewBean2.setTitle("韦德");
RecyclerViewBean listViewBean3 = new RecyclerViewBean();
listViewBean3.setAva("http://sss");
listViewBean3.setTitle("保罗");
for (int i = 0; i < 10; i++) {
list.add(listViewBean1);
list.add(listViewBean2);
list.add(listViewBean3);
}
return list;
}
/**
* 初始化 刷新集合数据
*/
private List<RecyclerViewBean> getUpdateList() {
mCount++;
List<RecyclerViewBean> list = new ArrayList<>();
//更新头像和标题
RecyclerViewBean listViewBean1 = new RecyclerViewBean();
listViewBean1.setAva("https://avatar.csdnimg.cn/1/2/4/3_weixin_37730482_1607556601.jpg");
listViewBean1.setTitle("张三_刷新" + mCount);
RecyclerViewBean listViewBean2 = new RecyclerViewBean();
listViewBean2.setAva("http://sss");
listViewBean2.setTitle("韦德");
RecyclerViewBean listViewBean3 = new RecyclerViewBean();
listViewBean3.setAva("http://sss");
listViewBean3.setTitle("保罗");
for (int i = 0; i < 10; i++) {
list.add(listViewBean1);
list.add(listViewBean2);
list.add(listViewBean3);
}
return list;
}
}
Adapter
public class RecycleViewUpdateAdapter extends RecyclerView.Adapter<RecycleViewUpdateAdapter.ViewHolder> {
private IRecyclerViewItemClick mClick;
public void setRecyclerViewItemClick(IRecyclerViewItemClick click) {
mClick = click;
}
private List<RecyclerViewBean> mList;
private LayoutInflater mInflater;
private Context mContext;
public RecycleViewUpdateAdapter(Context context, List<RecyclerViewBean> list) {
mContext = context;
mList = list;
mInflater = LayoutInflater.from(context);
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.recyclerview_item, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.textView.setText(mList.get(position).getTitle());
Glide.with(mContext).load(mList.get(position).getAva()).error(R.color.colorPrimary).into(holder.imageView);
//Item点击事件
holder.itemView.setOnClickListener(v -> {
if (null != mClick) {
mClick.onItemClick(v, position);
}
});
//Item长按事件
holder.itemView.setOnLongClickListener(v -> {
if (null != mClick) {
mClick.onItemLongClick(v, position);
}
return true;
});
Log.d("DiffUtilCallBack", "两个个参数的onBindViewHolder方法 position----:" + position);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) {
super.onBindViewHolder(holder, position, payloads);
if (payloads.isEmpty()) {//如果payloads集合为空 则执行两个参数的普通的onBindViewHolder方法
onBindViewHolder(holder, position);
} else {//如果payloads集合不为空 仅仅更新Item的某个控件
Bundle bundle = (Bundle) payloads.get(0);
if (null == bundle) return;
String ava = bundle.getString("KEY_AVA");
String title = bundle.getString("KEY_TITLE");
holder.textView.setText(title);
if (!TextUtils.isEmpty(ava)) {
Glide.with(mContext).load(ava).into(holder.imageView);
}
Log.d("DiffUtilCallBack", "三个参数的onBindViewHolder方法 ava----:" + ava);
Log.d("DiffUtilCallBack", "三个参数的onBindViewHolder方法 title----:" + title);
}
}
@Override
public int getItemCount() {
return mList.size();
}
/**
* ViewHolder类
*/
static class ViewHolder extends RecyclerView.ViewHolder {
private ImageView imageView;
private TextView textView;
public ViewHolder(View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.recyclerview_item_imageview);
textView = itemView.findViewById(R.id.recyclerview_item_textview);
}
}
/**
* 刷新数据 原始全量刷新
*/
public void updateData(List<RecyclerViewBean> list) {
if (null == list || list.size() == 0) return;
mList = list;
notifyDataSetChanged();
}
/**
* 刷新数据 DiffUtil差量刷新 同步
*/
public void updateData(List<RecyclerViewBean> oldList, List<RecyclerViewBean> newList) {
if (null == oldList || null == newList) return;
//1.获取DiffResult对象
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtilCallBack(oldList, newList));
//2.更新集合数据
mList = newList;
//3.使用DiffResult对象的dispatchUpdatesTo方法Adapter热更新
diffResult.dispatchUpdatesTo(this);
}
/**
* 获取Adapter中最新的集合数据
*/
public List<RecyclerViewBean> getData() {
return mList;
}
}
DiffUtil.Callback实现类
public class DiffUtilCallBack extends DiffUtil.Callback {
private List<RecyclerViewBean> mOldList;
private List<RecyclerViewBean> mNewList;
public DiffUtilCallBack(List<RecyclerViewBean> oldList, List<RecyclerViewBean> newList) {
mOldList = oldList;
mNewList = newList;
}
/**
* 获取旧集合数据量大小
*/
@Override
public int getOldListSize() {
return null == mOldList ? 0 : mOldList.size();
}
/**
* 获取新集合数据量大小
*/
@Override
public int getNewListSize() {
return null == mNewList ? 0 : mNewList.size();
}
/**
* 是否是相同的Item 返回值操作 根据具体情况 一般不操作ItemViewType 此方法返回true即可
*/
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return true;
}
/**
* 是否是相同的Contents
*/
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return (mOldList.get(oldItemPosition).getTitle()).equals(mNewList.get(newItemPosition).getTitle());
}
/**
* 更新Item的某一个控件View
*/
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
Bundle payload = new Bundle();
//头像是否更新
if (!mOldList.get(oldItemPosition).getAva().equals(mNewList.get(newItemPosition).getAva())) {
payload.putString("KEY_AVA", mNewList.get(newItemPosition).getAva());
}
//标题是否更新
if (!mOldList.get(oldItemPosition).getTitle().equals(mNewList.get(newItemPosition).getTitle())) {
payload.putString("KEY_TITLE", mNewList.get(newItemPosition).getTitle());
}
if (payload.size() == 0) {//如果没有变化 就传空
return null;
}
//更新Item的某个控件View
return payload;
}
}
刷新时,由于模拟刷新前和刷新后的数据一样。刷新时onBindViewHolder方法没有执行。
那么,如果刷新前和刷新后的数据不一样呢?我们测试一下。
修改数据,即刷新前和刷新后的数据不一样
mList = getUpdateList();//数据变化
刷新时,由于模拟刷新前和刷新后的数据每次都不一样。每次刷新时onBindViewHolder方法都会执行。
D/DiffUtilCallBack: onBindViewHolder position----:6
D/DiffUtilCallBack: onBindViewHolder position----:7
D/DiffUtilCallBack: onBindViewHolder position----:0
D/DiffUtilCallBack: onBindViewHolder position----:3
D/DiffUtilCallBack: onBindViewHolder position----:6
D/DiffUtilCallBack: onBindViewHolder position----:0
D/DiffUtilCallBack: onBindViewHolder position----:3
D/DiffUtilCallBack: onBindViewHolder position----:6
D/DiffUtilCallBack: onBindViewHolder position----:0
D/DiffUtilCallBack: onBindViewHolder position----:3
.........
总结
<1> DiffUtil类,提前给我们比较了刷新前后的集合变化。防止了全量刷新的操作。但是如果集合数据较大时,比较集合差异也是比较耗时的。因为涉及算法计算。因此 Google给我们提供了异步的DiffUtil 即DiffUtil。下面讲解AsyncListDiff的使用。
<2> DiffUtil.Callback类还有一个普通的getChangePayload方法,因为此方法是普通的方法,所以可以不重写。只是在需要的时候重写 比如更新Item的某个控件View。这个方法的返回值会在三个参数的onBindViewHolder方法回调。详情请看代码。
<3> 上述只为测试,正常情况下刷新后需要把刷新出来的数据复制给原有的集合,这样才是正常的流程。这里为了测试老集合一直用的都是老的数据集合。
三.AsyncListDiff工具类简介
1.说明
AsyncListDiff工具类是RecyclerView包中提供的一个工具类。主要用来替换DiffUtil。在子线程中操作比较集合变化。然后主线程更新UI。
2.源码解释
英文
Helper for computing the difference between two lists via on a background thread.
It can be connected to a {@link RecyclerView.Adapter RecyclerView.Adapter}, and will signal the adapter of changes between sumbitted lists.
翻译
帮助通过后台线程计算两个列表之间的差异。
它可以连接到{@link RecyclerView。适配器RecyclerView。},并将在提交列表之间的更改通知适配器。
四.AsyncListDiff工具类使用
Activity
public class RecyclerViewAsyncUpdateActivity extends AppCompatActivity {
private SwipeRefreshLayout mSwipeRefreshLayout;
private RecyclerView mRecyclerView;
private RecycleViewAsyncUpdateAdapter mRecycleViewAdapter;
private List<RecyclerViewBean> mList;
private int mCount = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recyclerview);
initView();
}
/**
* 初始化各种View
*/
private void initView() {
mSwipeRefreshLayout = findViewById(R.id.activity_recyclerview_srl);
initSwipeRefreshLayout();
mRecyclerView = findViewById(R.id.activity_recyclerview_recyclerView);
mList = getList();
//1.设置LinearLayoutManager ListView
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
//2.设置固定大小
mRecyclerView.setHasFixedSize(true);
//3.设置Adapter
mRecycleViewAdapter = new RecycleViewAsyncUpdateAdapter(this);
mRecyclerView.setAdapter(mRecycleViewAdapter);
mRecycleViewAdapter.submitList(mList);//AsyncListDiffer差量刷新 异步 初始化也要用这种方式添加数据
//4.RecyclerView点击事件
onRecyclerViewClick();
}
/**
* 初始化SwipeRefreshLayout
*/
private void initSwipeRefreshLayout() {
//设置进度View的组合颜色,在手指上下滑时使用第一个颜色,在刷新中,会一个个颜色进行切换
mSwipeRefreshLayout.setColorSchemeColors(Color.parseColor("#6200EE"), Color.parseColor("#3700B3"), Color.parseColor("#03DAC5"), Color.YELLOW, Color.BLUE);
//下拉刷新
mSwipeRefreshLayout.setOnRefreshListener(() -> {
//模拟6秒刷新时间
Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(() -> {
RecyclerViewAsyncUpdateActivity.this.runOnUiThread(() -> {
// mList = getUpdateList();//数据变化
mList = getList();//数据不变
mRecycleViewAdapter.submitList(mList);//AsyncListDiffer差量刷新 异步
});
mSwipeRefreshLayout.setRefreshing(false);//结束刷新
}, 6000);
});
}
/**
* RecyclerView点击事件
*/
private void onRecyclerViewClick() {
mRecycleViewAdapter.setRecyclerViewItemClick(new IRecyclerViewItemClick() {
@Override
public void onItemClick(View view, int position) {
Log.d("DiffUtilCallBack", "RecyclerView点击 position----:" + position);
Log.d("DiffUtilCallBack", "RecyclerView点击 title----:" + mList.get(position).getTitle());
}
@Override
public void onItemLongClick(View view, int position) {
Log.d("DiffUtilCallBack", "RecyclerView长按 position----:" + position);
Log.d("DiffUtilCallBack", "RecyclerView长按 title----:" + mList.get(position).getTitle());
}
});
}
/**
* 初始化 默认集合数据
*/
private List<RecyclerViewBean> getList() {
List<RecyclerViewBean> list = new ArrayList<>();
RecyclerViewBean listViewBean1 = new RecyclerViewBean();
listViewBean1.setAva("http://sss");
listViewBean1.setTitle("张三");
RecyclerViewBean listViewBean2 = new RecyclerViewBean();
listViewBean2.setAva("http://sss");
listViewBean2.setTitle("韦德");
RecyclerViewBean listViewBean3 = new RecyclerViewBean();
listViewBean3.setAva("http://sss");
listViewBean3.setTitle("保罗");
for (int i = 0; i < 10; i++) {
list.add(listViewBean1);
list.add(listViewBean2);
list.add(listViewBean3);
}
return list;
}
/**
* 初始化 刷新集合数据
*/
private List<RecyclerViewBean> getUpdateList() {
mCount++;
List<RecyclerViewBean> list = new ArrayList<>();
RecyclerViewBean listViewBean1 = new RecyclerViewBean();
listViewBean1.setAva("https://avatar.csdnimg.cn/1/2/4/3_weixin_37730482_1607556601.jpg");
listViewBean1.setTitle("张三_刷新" + mCount);
RecyclerViewBean listViewBean2 = new RecyclerViewBean();
listViewBean2.setAva("http://sss");
listViewBean2.setTitle("韦德");
RecyclerViewBean listViewBean3 = new RecyclerViewBean();
listViewBean3.setAva("http://sss");
listViewBean3.setTitle("保罗");
for (int i = 0; i < 10; i++) {
list.add(listViewBean1);
list.add(listViewBean2);
list.add(listViewBean3);
}
return list;
}
}
Adapter
public class RecycleViewAsyncUpdateAdapter extends RecyclerView.Adapter<RecycleViewAsyncUpdateAdapter.ViewHolder> {
private IRecyclerViewItemClick mClick;
public void setRecyclerViewItemClick(IRecyclerViewItemClick click) {
mClick = click;
}
private DiffUtil.ItemCallback<RecyclerViewBean> diffUtil = new DiffUtil.ItemCallback<RecyclerViewBean>() {
/**
* 是否是相同的Item 返回值操作 根据具体情况 一般不操作ItemViewType 此方法返回true即可
*/
@Override
public boolean areItemsTheSame(@NonNull RecyclerViewBean oldItem, @NonNull RecyclerViewBean newItem) {
return true;
}
/**
* 是否是相同的Contents
*/
@Override
public boolean areContentsTheSame(@NonNull RecyclerViewBean oldItem, @NonNull RecyclerViewBean newItem) {
return oldItem.getTitle().equals(newItem.getTitle());
}
/**
* 更新Item的某一个控件View
*/
@Nullable
@Override
public Object getChangePayload(@NonNull RecyclerViewBean oldItem, @NonNull RecyclerViewBean newItem) {
Bundle payload = new Bundle();
//头像是否更新
if (!oldItem.getAva().equals(newItem.getAva())) {
payload.putString("KEY_AVA", newItem.getAva());
}
//标题是否更新
if (!oldItem.getTitle().equals(newItem.getTitle())) {
payload.putString("KEY_TITLE", newItem.getTitle());
}
if (payload.size() == 0) {//如果没有变化 就传空
return null;
}
//更新Item的某个控件View
return payload;
}
};
private AsyncListDiffer<RecyclerViewBean> mDiffer;
private List<RecyclerViewBean> mList;
private LayoutInflater mInflater;
private Context mContext;
public RecycleViewAsyncUpdateAdapter(Context context) {
mContext = context;
mInflater = LayoutInflater.from(context);
mDiffer = new AsyncListDiffer<>(this, diffUtil);
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.recyclerview_item, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.textView.setText(mList.get(position).getTitle());
Glide.with(mContext).load(mList.get(position).getAva()).error(R.color.colorPrimary).into(holder.imageView);
//Item点击事件
holder.itemView.setOnClickListener(v -> {
if (null != mClick) {
mClick.onItemClick(v, position);
}
});
//Item长按事件
holder.itemView.setOnLongClickListener(v -> {
if (null != mClick) {
mClick.onItemLongClick(v, position);
}
return true;
});
Log.d("DiffUtilCallBack", "onBindViewHolder position----:" + position);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) {
if (payloads.isEmpty()) {//如果payloads集合为空 则执行两个参数的普通的onBindViewHolder方法
onBindViewHolder(holder, position);
} else {//如果payloads集合不为空 仅仅更新Item的某个控件
Bundle bundle = (Bundle) payloads.get(0);
if (null == bundle) return;
String ava = bundle.getString("KEY_AVA");
String title = bundle.getString("KEY_TITLE");
holder.textView.setText(title);
if (!TextUtils.isEmpty(ava)) {
Glide.with(mContext).load(ava).into(holder.imageView);
}
Log.d("DiffUtilCallBack", "三个参数的onBindViewHolder方法 ava----:" + ava);
Log.d("DiffUtilCallBack", "三个参数的onBindViewHolder方法 title----:" + title);
}
}
@Override
public int getItemCount() {
return mDiffer.getCurrentList().size();//默认 return mList.size();
}
/**
* ViewHolder类
*/
static class ViewHolder extends RecyclerView.ViewHolder {
private ImageView imageView;
private TextView textView;
public ViewHolder(View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.recyclerview_item_imageview);
textView = itemView.findViewById(R.id.recyclerview_item_textview);
}
}
/**
* 刷新数据 AsyncListDiffer差量刷新 异步
*/
public void submitList(List<RecyclerViewBean> data) {
//1.AsyncListDiffer提交差量
mDiffer.submitList(data);
//2.更新集合数据
mList = data;
}
}
刷新时,由于模拟刷新前和刷新后的数据一样。刷新时onBindViewHolder方法没有执行。
那么,如果刷新前和刷新后的数据不一样呢?我们测试一下。
修改数据,即刷新前和刷新后的数据不一样
mList = getUpdateList();//数据变化
刷新时,由于模拟刷新前和刷新后的数据每次都不一样。每次刷新时onBindViewHolder方法都会执行。
D/DiffUtilCallBack: onBindViewHolder position----:6
D/DiffUtilCallBack: onBindViewHolder position----:7
D/DiffUtilCallBack: onBindViewHolder position----:0
D/DiffUtilCallBack: onBindViewHolder position----:3
D/DiffUtilCallBack: onBindViewHolder position----:6
D/DiffUtilCallBack: onBindViewHolder position----:0
D/DiffUtilCallBack: onBindViewHolder position----:3
D/DiffUtilCallBack: onBindViewHolder position----:6
D/DiffUtilCallBack: onBindViewHolder position----:0
D/DiffUtilCallBack: onBindViewHolder position----:3
.........
注意
使用AsyncListDiffer时,和使用DiffUtil或者普通的RecyclerView有几点不同需要注意。
<1> Adaper中getItemCount()方法有差别
@Override
public int getItemCount() {
return mDiffer.getCurrentList().size();//默认 return mList.size();
}
<2> 设置初始值有差别
普通
mRecycleViewAdapter = new RecycleViewUpdateAdapter(this, mList);
mRecyclerView.setAdapter(mRecycleViewAdapter);
即:在new Adapter时传参集合数据。
AsyncListDiffer
mRecycleViewAdapter = new RecycleViewAsyncUpdateAdapter(this);
mRecyclerView.setAdapter(mRecycleViewAdapter);
mRecycleViewAdapter.submitList(mList);//AsyncListDiffer差量刷新 异步 初始化也要用这种方式添加数据