废话不多说,上来就先看效果吧。点击“模拟刷新”按钮完成第二、三项列表中的描述文字和下面的滚动动画的刷新。第二次进入直接下拉到列表底部点击“模拟刷新”按钮,同样可以完成定向刷新。这应该是我们期待的效果吧。这样的功效就是普通的RecyclerView配合DiffUtil来实现的定向局部刷新。
DiffUtil的用法很简单网上随便都有很多教程,今天主要就是放一个完整的例子出来,方便以后查阅和使用。
下面就开始介绍下这个工具类的使用步骤:
1.写一个bean来盛放item的数据项,详情见TestBean.java文件:我们的例子中每个item有四个属性,其中id就是区分每个item刷新前后是否是相同的item(具体在DiffCallback的areItemsTheSame方法中使用),bean的属性如下:
/**
*
* @param id 记录每个Item的id
* @param name item的名字
* @param desc item的描述信息
* @param animText item的动画
*/
public TestBean(int id, String name, String desc, String animText) {
this.itemID = id;
this.name = name;
this.desc = desc;
this.textAnim = animText;
}
2.建立旧的、新的需要刷新的数据集合,详情见TestMainActivity.java:
建立两个集合模拟新旧数据集,然后把新旧数据集传到DiffUtil.calculateDiff()方法中即可比较新旧数据集中的不同数据项。
private List mDatas; //旧的数据集合
private List mNewDatas; //新的数据集合
/**
* 数据量小可以直接这么写,
* 计算新旧数据集合的不同,进而根据这个不同进行更新
*/
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallBack(mDatas, mNewDatas), true);
diffResult.dispatchUpdatesTo(mAdapter);
mAdapter.setDatas(mNewDatas);
3.核心步骤:继承DiffUtil.Callback ,实现5个方法,详情见DiffCallBack.java,作用是将第2步中传过来的新旧数据集进行比较,并通过getChangePayload()方法将变化的数据放到payloads参数中传给onBindViewHolder的第三个参数,进而更新界面上的数据项;
为了拿到从上面那个DiffCallBack中传过来的payloads参数,所以需要在DiffAdapter中重写onBindViewHolder(DiffVH holder, int position, List payloads)三个参数的方法,拿到payloads的数据更新界面即可,详情见DiffAdapter.java:
- getOldListSize():旧数据集的长度。
- getNewListSize():新数据集的长度
- areItemsTheSame():判断是否是同一个Item。
- areContentsTheSame():如果是通一个Item,此方法用于判断是否同一个 Item 的内容也相同。
- getChangePayload():
最后一个方法的调用情况是:areItemsTheSame()返回true而areContentsTheSame()返回false,也就是说两个对象代表的数据是一条,但是内容更新了。在getChangePayload()方法中,你要给出具体的变化。这里我使用的Bundle,具体使用什么方式来表示数据的更新并不重要,重要的是在这个方法中你把更新情况存入一个对象后,在后面还能从同一个对象中把更新的情况取出来。也就是在DiffAdapter中重写onBindViewHolder(DiffVH holder, int position, List payloads)方法,从payloads参数中,取出来我们刚刚在新数据集合中更新的数据项。
总共三个步骤,前两步没有什么好说的,就是创建新旧数据集合,并且使用现成的DiffUtil工具进行计算差集,核心是第三部中重写的几个方法:下面是具体代码,注释很清楚了:
DiffCallBack中重写的五个方法就是比较下新旧数据集的不同:
/**
* 完成数据集更新的核心回调
* 判断新旧Item是否相等,里面的属性值是否需要更新
*/
public class DiffCallBack extends DiffUtil.Callback {
private List mOldDatas, mNewDatas;
public DiffCallBack(List mOldDatas, List mNewDatas) {
this.mOldDatas = mOldDatas;
this.mNewDatas = mNewDatas;
}
@Override
public int getOldListSize() {
return mOldDatas != null ? mOldDatas.size() : 0;
}
@Override
public int getNewListSize() {
return mNewDatas != null ? mNewDatas.size() : 0;
}
//判断是不是同一个item
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return mOldDatas.get(oldItemPosition).getItemID() == mNewDatas.get(newItemPosition).getItemID();
}
//判断Item对象里面的属性值是否变化了,变化了返回false
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
TestBean beanOld = mOldDatas.get(oldItemPosition);
TestBean beanNew = mNewDatas.get(newItemPosition);
if (!beanOld.getName().equals(beanNew.getName())) {
return false; //如果有内容不同,就返回false
}
if (!beanOld.getDesc().equals(beanNew.getDesc())) {
return false;
}
if (!beanOld.getTextAnim().equals(beanNew.getTextAnim())) {
return false;
}
return true;
}
/**
* 新值存入
*
* 高效的局部更新,判断新旧数据集每个Item里面的属性是否相同,不同的话将新数据集中的数据取出来,放到payLoad中
* 然后从Adapter的onBindViewHolder(DiffVH holder, int position, List payloads)这个方法中取出更新的值,
* 设置到相对应的Item的属性上面,完成更新
*
* 调用情况是:areItemsTheSame()返回true而areContentsTheSame()返回false,也就是说两个对象代表的数据是一条,但是内容更新了。
* @param oldItemPosition
* @param newItemPosition
* @return
*/
@Nullable
@Override
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.getName().equals(newBean.getName())) {
payload.putString("KEY_NAME", newBean.getDesc());
}
if (!oldBean.getTextAnim().equals(newBean.getTextAnim())) {
payload.putString("KEY_ANIM_TEXT", newBean.getTextAnim());
}
if (payload.size() == 0)//如果没有变化 就传空
return null;
return payload;
}
}
在DiffAdapter中重写onBindViewHolder的三个参数的方法就是将上面的新数据集合中比较出来的不同的数据设置到界面上,下面这个就是整个Adapter:
class DiffAdapter extends RecyclerView.Adapter{
private List mDatas;
private Context mContext;
private LayoutInflater mInflater;
private ScaleInAnimation mSelectAnimation = new ScaleInAnimation();
public DiffAdapter(Context mContext, List mDatas) {
this.mContext = mContext;
this.mDatas = mDatas;
mInflater = LayoutInflater.from(mContext);
}
public void setDatas(List mDatas) {
this.mDatas = mDatas;
}
@Override
public DiffVH onCreateViewHolder(ViewGroup parent, int viewType) {
return new DiffVH(mInflater.inflate(R.layout.item_diff, parent, false));
}
@Override
public void onBindViewHolder(DiffVH holder, int position) {
TestBean bean = mDatas.get(position);
holder.tv1.setText(bean.getName());
holder.tv2.setText(bean.getDesc());
holder.switchView.startTextChangeAnim(bean.getTextAnim());
}
/**
* 重写这个方法,从DiffCallBack的getChangePayload()方法中取出存入的新值
* @param holder
* @param position
* @param payloads
*/
@Override
public void onBindViewHolder(DiffVH holder, int position, List payloads) {
if (payloads.isEmpty()) {
onBindViewHolder(holder, position);
} else {
Bundle payload = (Bundle) payloads.get(0);//取出我们在getChangePayload()方法返回的bundle
TestBean bean = mDatas.get(position);//取出新数据源,(可以不用,data也是新的 也可以用)
for (String key : payload.keySet()) {
switch (key) {
case "KEY_NAME":
//这里可以用payload里的数据,不过data也是新的 也可以用
holder.tv1.setText(bean.getName());
break;
case "KEY_DESC":
holder.tv2.setText(bean.getDesc());
break;
case "KEY_ANIM_TEXT":
holder.switchView.startTextChangeAnim(bean.getTextAnim());
break;
default:
break;
}
}
}
}
@Override
public void onViewAttachedToWindow(DiffVH holder) {
super.onViewAttachedToWindow(holder);
addAnimation(holder);
}
private void addAnimation(DiffVH holder) {
for (Animator anim : mSelectAnimation.getAnimators(holder.itemView)) {
anim.setDuration(300).start();
anim.setInterpolator(new LinearInterpolator());
}
}
@Override
public int getItemCount() {
return mDatas != null ? mDatas.size() : 0;
}
class DiffVH extends RecyclerView.ViewHolder {
TextView tv1, tv2;
TextSwitchView switchView;
public DiffVH(View itemView) {
super(itemView);
tv1 = (TextView) itemView.findViewById(R.id.tv1);
tv2 = (TextView) itemView.findViewById(R.id.tv2);
switchView = (TextSwitchView) itemView.findViewById(R.id.switchView);
tv1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(mContext,"1111111111",Toast.LENGTH_SHORT).show();
}
});
tv2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(mContext,"22222222222",Toast.LENGTH_SHORT).show();
}
});
}
}
}
上面所有的都是很常规的使用,除了public void onBindViewHolder(DiffVH holder, int position, List payloads)三个参数的重写就是取出来新数据,设置到item上。
完整的项目地址:
https://github.com/buder-cp/base_component_learn/tree/master/diffut
注意项目里面有4个java文件和本文所涉及到的东西没有关系,
TextSwitchView.java 是例子中每个Item下面的循环滚动文字;
BamAnim.java BamLinearLayout.java 是每个item的点击动画效果;
ScaleAnimation.java 是每个item加载进来时候的动画效果。这四个可以不看,或者直接删除都是可以的。