DiffUtils是用来更新RecyclerView的工具,使用DiffUtils可以代替手动刷新RecyclerView。
使用方法
一、确定Item是否更新的规则(数据变化了,但是不一定需要更新UI界面)
创建一个类继承 DiffUtil.Callback ,重新四个abstract方法
class DiffCallBack(private val lastData: ArrayList<TestBean>, private val newData: ArrayList<TestBean>) :
DiffUtil.Callback() {
override fun getOldListSize(): Int {
return lastData.size
}
override fun getNewListSize(): Int {
return newData.size
}
/**
* 用来判断是否是同一个item
*/
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
LogV("oldItemPosition = $oldItemPosition , newItemPosition = $newItemPosition")
return lastData[oldItemPosition].name == newData[newItemPosition].name
}
/**
* 只有 areItemsTheSame 为 True的时候才会调用这个方法
* 用来判断是否UI需要改变
*/
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
LogV("oldItemPosition = $oldItemPosition , newItemPosition = $newItemPosition")
return lastData[oldItemPosition] == newData[newItemPosition]
}
}
二、计算新老数据集的差异,并且通知RecyclerView刷新
//setDatas函数里面就不需要写notifyDataSetChanged()了,在数据更新的时候会重新走onbindView,只要数据是新的就行了。
mAdapter.setDatas(newData)
//计算新老数据集的差异
val diffResult = DiffUtil.calculateDiff(DiffCallBack(oldData, newData), true)
//通知RecyclerView刷新
//推测是通过消息机制来刷新的,即使setDatas函数写在dispatchUpdatesTo后面, 也同样可以成功更新UI。
diffResult.dispatchUpdatesTo(mAdapter)
使用dispatchUpdatesTo通知recyclerView刷新的时候,其实就是通过Adapter的定向刷新方法来更新数据的。
AdapterListUpdateCallback.class
@Override
public void onInserted(int position, int count) {
mAdapter.notifyItemRangeInserted(position, count);
}
@Override
public void onRemoved(int position, int count) {
mAdapter.notifyItemRangeRemoved(position, count);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
mAdapter.notifyItemMoved(fromPosition, toPosition);
}
@Override
public void onChanged(int position, int count, Object payload) {
mAdapter.notifyItemRangeChanged(position, count, payload);
}
高阶用法,ViewHolder的局部刷新(究极节约资源大法)
比如一个ViewHolder里面有一个TV(TextView)跟一个IV(ImageView),IV 的图片链接没变化,TV的文字变化了,按照往常处理方法更新ViewHolder,通常是重走bindView方法,把ViewHolder里面的所有的View都重新赋值,但是使用DiffUtils的高阶用法之后,就可以只针对TV更新,不为IV重新设置图片链接了。
一 、在继承 DiffUtil.Callback的时候,重写getChangePayload方法
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
return super.getChangePayload(oldItemPosition, newItemPosition)
}
getChangePayload方法在areItemsTheSame 返回 true , areContentsTheSame返回false的时候调用,即 :新旧数据对应同一个Item,但是 content发生了变化,需要更新 部分或 全部 的UI。
二 、我们判断 那些 UI 绑定的数据发生变化了,然后记录下来,在返回值里面返回,可以使用Bundle或其它数据类型记录变化的数据。
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
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;
}
三、重写RecyclerView里面的onBindViewHolder(DiffVH holder, int position, List payloads)方法,我们在getChangePayload 方法里的返回值就是 这个三参数 onBindViewHolder的第三个参数,我们根据哪些数据发生了变化,只更新对应的View即可,不需要重写给ViewHolder里面的View赋值了。
public void onBindViewHolder(DiffVH holder, int position, 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;
}
}
}
}