使用场景:对于Android 的RecyclerView中 的数据刷新,之前自己一直调用notifyDataSetChanged()而这样会Item全重新绘制,影响界面展示,当数据变化多的时候,会ARN出现,而由于集合发生变化的时候,只可以调用notifyDataSetChanged方法进行整个界面的刷新,并不能根据集合的变化为每一个变化的元素添加动画。所以这里就有了DiffUtil来解决这个问题。
RecyclerView对于每个Item的动画是以不同方式刷新的:
而对于连续的几个Item的刷新,可以调用:
DiffUtil的作用,就是找出集合中每一个Item发生的变化,然后对每个变化给予对应的刷新。
这个DiffUtil使用的是Eugene Myers的差别算法,这个算法本身不能检查到元素的移动,也就是移动只能被算作先删除、再增加,而DiffUtil是在算法的结果后再进行一次移动检查。假设在不检测元素移动的情况下,算法的时间复杂度为O(N + D2),而检测元素移动则复杂度为O(N2)。所以,如果集合本身就已经排好序,可以不进行移动的检测提升效率。
下面我们一起来看看这个工具怎么用。
首先对于每个Item,数据是一个Student对象:
public class Assembly implements Serializable,Cloneable,Comparator {
private String qrContent;//二维码内容
private String value;//数量
private String batchCode;//生产批号 生产日期 YYYY-MM-DD
private String machineCode;//机台号
private String productModel;//规格型号
private String ccdd;//库存地点
private String ccdd1;//目标库存地点
private String team;//班组
private String substitutionProcess;//备注
注意对数据源的克隆,不然数操作的数据源同一个引用,就不会有改变
/
public void setData(List s){
currentPlateOdi=new ArrayList<>(s);
}
public List getData(){
return currentPlateOdi;
}
@Override
public RecyclerView.ViewHolder onCompatCreateViewHolder(View realContentView, int viewType) {
View view;
switch (viewType){
case VIEW_HEDA:
return new HeadItemViewHolder(realContentView);
case VIEW_CONTENT:
return new ItemViewHolder(realContentView);
}
return null;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
//处理业务逻辑
}
/
*
重写此方法当有数据更新会进入
*
*/
@Override
public void onCompatBindViewHolder(RecyclerView.ViewHolder holder, int position, List
设置调用diffutil在activity和fragmen
if(pullToRefreshRecyclerView.getAdapter()==null){
adapter=new FittingStockoutDetialAdapter(activity,currentAssemblys);
adapter.canoperation=canoperation;
pullToRefreshRecyclerView.setAdapter(adapter);
}else{//
对diffutil的调用
List olddata=adapter.getData();
DiffUtil.DiffResult diffResult=DiffUtil.calculateDiff(new MyCallback(olddata,currentAssemblys),true);
adapter.setData(currentAssemblys);
adapter.canoperation=canoperation;
diffResult.dispatchUpdatesTo(adapter);
}
接着我们看看MyCallback接口的实现类如何定义:
package com.example.liujiancheng.tnsapsubmitoffice.ui.Fitting.diffutilcallback;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.util.DiffUtil;
import com.example.liujiancheng.tnsapsubmitoffice.ui.Fitting.entity.Assembly;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
/**
* Created by tnsap10 on 2018/12/6.
*/
public class MyCallback extends DiffUtil.Callback {
private List oldlist,newlist;
public MyCallback(List old,List newl){
oldlist=old;
newlist=newl;
}
public List getNewlist() {
return newlist;
}
@Override
public int getNewListSize() {
return newlist.size();
}
public List getOldlist() {
return oldlist;
}
@Override
public int getOldListSize() {
return oldlist.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return oldlist.get(oldItemPosition).getQrContent().equals(newlist.get(newItemPosition).getQrContent());
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return oldlist.get(oldItemPosition).getValue().equals(newlist.get(newItemPosition).getValue());
}
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
Assembly assembly=newlist.get(newItemPosition);
Bundle bundle=new Bundle();
bundle.putString("value",assembly.getValue());
return bundle;
}
}
这里根据学号判断是否同一个Item,根据姓名判断这个Item是否有被修改。
实际上,这个Callback抽象类还有一个方法getChangePayload(),这个方法的作用是我们可以通过这个方法告诉Adapter对这个Item进行局部的更新而不是整个更新。
先要知道这个payload是什么?payload是一个用来描述Item变化的对象,也就是我们的Item发生了哪些变化,这些变化就封装成一个payload,所以我们一般可以用Bundle来充当。
接着,getChangePayload()方法是在areItemsTheSame()返回true,而areContentsTheSame()返回false时被回调的,也就是一个Item的内容发生了变化,而这个变化有可能是局部的(例如微博的点赞,我们只需要刷新图标而不是整个Item)。所以可以在getChangePayload()中封装一个Object来告诉RecyclerView进行局部的刷新。
假设上例中学号和姓名用不同的TextView显示,当我们修改了一个学号对应的姓名时,局部刷新姓名即可(这里例子可能显得比较多余,但是如果一个Item很复杂,用处就比较大了):
先是重写Callback中的该方法:
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
Assembly assembly=newlist.get(newItemPosition);
Bundle bundle=new Bundle();
bundle.putString("value",assembly.getValue());
return bundle;
}
}
返回的这个对象会在什么地方收到呢?实际上在RecyclerView.Adapter中有两个onBindViewHolder方法,一个是我们必须要重写的,而另一个的第三个参数就是一个payload的列表:
1 @Override
2 public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads) {}
所以我们只需在Adapter中重写这个方法,如果List为空,执行原来的onBindViewHolder进行整个Item的更新,否则根据payloads的内容进行局部刷新:
1 @Override
2 public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads) {
3 if (payloads.isEmpty()) {
4 onBindViewHolder(holder, position);
5 } else {
6 MyViewHolder myViewHolder = (MyViewHolder) holder;
7 Bundle bundle = (Bundle) payloads.get(0);
8 if (bundle.getString(NAME_KEY) != null) {
9 myViewHolder.name.setText(bundle.getString(NAME_KEY));
10 myViewHolder.name.setTextColor(Color.BLUE);
11 }
12 }
13 }
这里的payloads不会为null,所以直接判断是否为空即可。
这里注意:如果RecyclerView中加载了大量数据,那么算法可能不会马上完成,要注意ANR的问题,可以开启单独的线程进行计算。