Android开发学习之路-DiffUtil使用

使用场景:对于Android 的RecyclerView中 的数据刷新,之前自己一直调用notifyDataSetChanged()而这样会Item全重新绘制,影响界面展示,当数据变化多的时候,会ARN出现,而由于集合发生变化的时候,只可以调用notifyDataSetChanged方法进行整个界面的刷新,并不能根据集合的变化为每一个变化的元素添加动画。所以这里就有了DiffUtil来解决这个问题。

 

RecyclerView对于每个Item的动画是以不同方式刷新的:

  • notifyItemInserted //插入新的item

     

  • notifyItemChanged //更改数据Item
  • notifyItemMoved //移动当前Item
  • notifyItemRemoved //删除当前Item

而对于连续的几个Item的刷新,可以调用:

  • notifyItemRangeChanged //批量进行更改
  • notifyItemRangeInserted //批量插入
  • notifyItemRangeRemoved //批量移除

 

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 payloads) {
       if(payloads.isEmpty()){
           onBindViewHolder(holder,position);
       }else{
        if(holder instanceof ItemViewHolder){
            Bundle b=(Bundle)payloads.get(0);
            if(!DataUtil.isEmpty(b.getString("value"))){
                ((ItemViewHolder) holder).value.setText(b.getString("value"));
            }

        }

       }
    }

}
 
  

 

设置调用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的问题,可以开启单独的线程进行计算。

 

你可能感兴趣的:(Android,局部数据更新)