RecyclewView 局部数据刷新(DiffUtil )
最近在项目中遇到一个需求,在加载首页列表信息时,先是要加载缓存内容[写在文件中],如果网络请求有数据,则替换缓存内容,并显示新的内容。需求本来很简单,直接一个RecyclerView解决了,写完代码就提交了,请原谅数据上的美女图,公司的项目就是如此。其实缓存中的数据与新数据,只有第三条发生了改变,其他的都一毛一样;而且你在刷新的时候,会发现一道白光闪过,有人抱怨体验不太好,那好,我改改,改完之后,变成这个样子了:
左边是不太讲究的直接RecyclewView.Adapter.notifyItemRangeChanged()的效果,右边的是使用了android.support.v7.util.DiffUtil这个帮助类实现的效果,可以说,差距还是比较大,那么今天就好好说一下这个DiffUtil的作用。
说明
这个DiffUtil存在的意义,是专门为RecyclerView更新item设计的。回想一下,我们当初从ListView中,使用BaseAdapter.notifyDataChanged()无脑全屏刷新,到RecyclerView.Adapter提供了notifyItem**系列,似的RecyclerView刷新时,可以精确到具体的目标item;最后到使用DiffUtil.DiffResult可以精确到某个Item上某个具体的TextView,或者ImageView的刷新。一步一步走来,你会发现,我们可以控制刷新的可视范围越来越精确,进而会使我们的app每次刷新消耗的资源越来越小,效果越来越nice。扯了这么多,还是还来看看传说中的DiffUtil的使用方法吧。
使用
首先我们定义User对象,作为每个RecyclerView 条目的数据载体:
public class User {
// 用户Id
public int id ;
// 用户名称
public String name;
//用户图像
public String url ;
//用户年龄
public int age;
}
定义一个Callback继承DiffUtil.Callback,这个比较重要:
public class UserItemDiffCallBack extends DiffUtil.Callback {
//旧的数据集合
private List<User> mOldUserList;
//新的数据集合
private List<User> mNewUserList;
//构造方法 传入旧的数据结构和新的数据结构
public UserItemDiffCallBack(List<User> oldUserList, List<User> newUserList) {
this.mOldUserList = oldUserList;
this.mNewUserList = newUserList;
}
//获取旧的数据量大小
@Override
public int getOldListSize() {
return null == mOldUserList ? 0 : mOldUserList.size();
}
//获取新的数据量大小
@Override
public int getNewListSize() {
return null == mNewUserList ? 0 : mNewUserList.size();
}
//判断两个条目是否是一致的
//在真实的项目中,我们一般使用id或者index搜索来判断两条item是否一致
//如果我们的id一样,在系统里面我就认为两个数据记录是一样的
@Override
public boolean areItemsTheSame(int oldPosition, int newPosition) {
return mOldUserList.get(oldPosition).id == mNewUserList.get(newPosition).id;
}
//这个需要areItemsTheSame 返回true时才调用
//即使我们的id是一致的,我们在系统中是同一个对象,但是的name可能更新,或者图像可能更新了
//这里可以填写自己的逻辑,如果图像是一致的,我就认为内容没有变化
@Override
public boolean areContentsTheSame(int oldPosition, int newPosition) {
return TextUtils.equals(mOldUserList.get(oldPosition).url, mNewUserList.get(newPosition).url);
}
//这个调用比较奇葩,要求也蛮多的,它需要areItemsTheSame()返回true,说明是同一条数据
//但是又需要areContentsTheSame()返回false,告诉你虽然我们是同一条数据,但是我们也有不同的
//它返回的是Object对象,我这里是返回的是Boolean对象,等会告诉你怎么用这个对象
//当然了,你也可以返回任意的对象,到时候装换一下就可以了。
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
User newUserEntity = mNewUserList.get(newItemPosition);
return oldUserEntity.url.equals(newUserEntity.url);
}
}
- UserItemDiffCallBack完成之后,需要在RecyclerView.Adapter中调用含有三个参数的onBindViewHolder方法:
// 这里的 payloads就是UserItemDiffCallBack中getChangePayload中返回的Object集合
// 如果某个条目没有调用UserItemDiffCallBack#getChangePayload方法,那么那个条目对应的
// onBindViewHolder中payloads就会为空数组对象
//由于我返回的判断新旧数据的url是否相同,所以直接更新一个item的照片就可以了,对于Item其他的TextView对应的
//name和age,数据没有变化,就没有必要更新了。
//这里就是上面所说的,可以精确到某个View的更新了,比notifyItemChanged更加有效了。
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) {
if (payloads.isEmpty()) {
super.onBindViewHolder(holder, position, payloads);
return;
}
ImageLoader.load(holder.iv, mList.get(position).url);
}
最后,有新旧数据更新时,在RecyclerView.Adapter中添加方法
public void setData(List<User> userList) {
// 获取DiffResut结果
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new UserItemDiffCallBack(mList, userList));
//使用DiffResult分发给apdate热更新
diffResult.dispatchUpdatesTo(this);
//按照DiffeResult定义好的逻辑,更新数据
mList.clear();
mList.addAll(userList);
}
使用时:
List<User> tempList = getNewUserList();
mCommonAdapter2.setData(tempList);
此时更新数据时,将不再会有满屏白光闪烁,只更新数据变换的项,这个比notifyItemChanged(int position)帅气多了。
对于DiffUtil,基本流程图可以看一下下图:
本篇算是一个小小的开端,下一篇我打算总结一下DiffUtil的基本原理。
使用DiffUtil遇到的 异常:
Inconsistency detected. Invalid view holder adapter positionViewHolder
描述:
使用了DiffUtil之后,发现后台上报了一些异常,是关于RecyclerView滑动异常导致界面崩溃的,按照一些博客上的步骤也没有解决问题。这个问题以前是没有的,应该是DiffUtils使用不当造成的。后来是google发现了问题所在,地址为:https://android.jlelse.eu/smart-way-to-update-recyclerview-using-diffutil-345941a160e0
是我新旧数据添加时先后顺序存在问题,老的逻辑为先比较后数据更改:
public void setData(List<User> userList) {
// 获取DiffResut结果
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new UserItemDiffCallBack(mList, userList));
//使用DiffResult分发给apdate热更新
diffResult.dispatchUpdatesTo(this);
//按照DiffeResult定义好的逻辑,更新数据
mList.clear();
mList.addAll(userList);
}
按照博客上更改为先更改数据后比较数据:
public void setData(List<User> userList) {
// 获取DiffResut结果
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new UserItemDiffCallBack(mList, userList));
//按照DiffeResult定义好的逻辑,更新数据
mList.clear();
mList.addAll(userList);
//使用DiffResult分发给apdate热更新
diffResult.dispatchUpdatesTo(this);
}
暂时没有出现问题。