当前位置: 移动技术网 > IT编程>移动开发>Android > Android开发之DiffUtil的使用详解

Android开发之DiffUtil的使用详解

2019年07月24日  | 移动技术网IT编程  | 我要评论

新浪cn邮箱,中华許圩网,btchina

写在前面的话

diffutil是一个查找集合变化的工具类,是搭配recyclerview一起使用的,如果你还不了解recyclerview,可以阅读一些资料,这里就不介绍了。

先放效果图:

可以看到,当我们点击按钮的时候,这个recyclerview所显示的集合发生了改变,有的元素被增加了(8.jason),也有的元素被移动了(3.rose),甚至是被修改了(2.fndroid)。

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

     notifyiteminserted

     notifyitemchanged

     notifyitemmoved

     notifyitemremoved

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

     notifyitemrangechanged

     notifyitemrangeinserted

     notifyitemrangeremoved

而由于集合发生变化的时候,只可以调用notifydatasetchanged方法进行整个界面的刷新,并不能根据集合的变化为每一个变化的元素添加动画。所以这里就有了diffutil来解决这个问题。

diffutil的作用,就是找出集合中每一个item发生的变化,然后对每个变化给予对应的刷新。

这个diffutil使用的是eugene myers的差别算法,这个算法本身不能检查到元素的移动,也就是移动只能被算作先删除、再增加,而diffutil是在算法的结果后再进行一次移动检查。假设在不检测元素移动的情况下,算法的时间复杂度为o(n + d2),而检测元素移动则复杂度为o(n2)。所以,如果集合本身就已经排好序,可以不进行移动的检测提升效率。 

下面我们一起来看看这个工具怎么用。

首先对于每个item,数据是一个student对象:

class student {
 private string name;
 private int num;

 public student(string name, int num) {
  this.name = name;
  this.num = num;
 }

 public string getname() {
  return name;
 }

 public void setname(string name) {
  this.name = name;
 }

 public int getnum() {
  return num;
 }

 public void setnum(int num) {
  this.num = num;
 }
}

接着我们定义布局(省略)和适配器:

class myadapter extends recyclerview.adapter {
  private arraylist<student> data;

  arraylist<student> getdata() {
   return data;
  }

  void setdata(arraylist<student> data) {
   this.data = new arraylist<>(data);
  }

  @override
  public recyclerview.viewholder oncreateviewholder(viewgroup parent, int viewtype) {
   view itemview = layoutinflater.from(recyclerviewactivity.this).inflate(r.layout.itemview, null);
   return new myviewholder(itemview);
  }

  @override
  public void onbindviewholder(recyclerview.viewholder holder, int position) {
   myviewholder myviewholder = (myviewholder) holder;
   student student = data.get(position);
   myviewholder.tv.settext(student.getnum() + "." + student.getname());
  }

  @override
  public int getitemcount() {
   return data.size();
  }

  class myviewholder extends recyclerview.viewholder {
   textview tv;

   myviewholder(view itemview) {
    super(itemview);
    tv = (textview) itemview.findviewbyid(r.id.item_tv);
   }
  }
 }

初始化数据集合:

private void initdata() {
  students = new arraylist<>();
  student s1 = new student("john", 1);
  student s2 = new student("curry", 2);
  student s3 = new student("rose", 3);
  student s4 = new student("dante", 4);
  student s5 = new student("lunar", 5);
  students.add(s1);
  students.add(s2);
  students.add(s3);
  students.add(s4);
  students.add(s5);
 }

接着实例化adapter并设置给recyclerview:

@override
 protected void oncreate(bundle savedinstancestate) {
  super.oncreate(savedinstancestate);
  setcontentview(r.layout.activity_recycler_view);
  initdata();
  recyclerview = (recyclerview) findviewbyid(r.id.rv);
  recyclerview.setlayoutmanager(new linearlayoutmanager(this));
  adapter = new myadapter();
  adapter.setdata(students);
  recyclerview.setadapter(adapter);
 }

这些内容都不是本篇的内容,但是,需要注意到的一个地方是adapter的定义:

class myadapter extends recyclerview.adapter {
  private arraylist<student> data;

  arraylist<student> getdata() {
   return data;
  }

  void setdata(arraylist<student> data) {
   this.data = new arraylist<>(data);
  }

  // 省略部分代码
   ...... 
 }

这里的setdata方法并不是直接将arraylist的引用保存,而是重新的建立一个arraylist,先记着,后面会解释为什么要这样做。

diffutil的使用方法:

当鼠标按下时,修改arraylist的内容:

public void change(view view) {
  students.set(1, new student("fndroid", 2));
  students.add(new student("jason", 8));
  student s2 = students.get(2);
  students.remove(2);
  students.add(s2);

  arraylist<student> old_students = adapter.getdata();
  diffutil.diffresult result = diffutil.calculatediff(new mycallback(old_students, students), true);
  adapter.setdata(students);
  result.dispatchupdatesto(adapter);
 }

2-6行是对集合进行修改,第8行先获取到adapter中的集合为旧的数据。

重点看第9行调用diffutil.calculatediff方法来计算集合的差别,这里要传入一个callback接口的实现类(用于指定计算的规则)并且把新旧数据都传递给这个接口的实现类,最后还有一个boolean类型的参数,这个参数指定是否需要进行move的检测,如果不需要,如果有item移动了,会被认为是先remove,然后insert。这里指定为true,所以就有了动图显示的移动效果。

第10行重新将新的数据设置给adapter。

第11行调用第9行得到的diffresult对象的dispatchupdatesto方法通知recyclerview刷新对应发生变化的item。

这里回到上面说的setdata方法,因为我们在这里要区分两个集合,如果在setdata方法中直接保存引用,那么在2-6行的修改就直接修改了adapter中的集合了(java知识)。

如果设置不检查item的移动,效果如下:

接着我们看看callback接口的实现类如何定义:

private class mycallback extends diffutil.callback {
  private arraylist<student> old_students, new_students;

  mycallback(arraylist<student> data, arraylist<student> students) {
   this.old_students = data;
   this.new_students = students;
  }

  @override
  public int getoldlistsize() {
   return old_students.size();
  }

  @override
  public int getnewlistsize() {
   return new_students.size();
  }

  // 判断item是否已经存在
  @override
  public boolean areitemsthesame(int olditemposition, int newitemposition) {
   return old_students.get(olditemposition).getnum() == new_students.get(newitemposition).getnum();
  }

  // 如果item已经存在则会调用此方法,判断item的内容是否一致
  @override
  public boolean arecontentsthesame(int olditemposition, int newitemposition) {
   return old_students.get(olditemposition).getname().equals(new_students.get(newitemposition).getname());
  }
 }

这里根据学号判断是否同一个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) {
   student newstudent = newstudents.get(newitemposition);
   bundle diffbundle = new bundle();
   diffbundle.putstring(name_key, newstudent.getname());
   return diffbundle;
  }

返回的这个对象会在什么地方收到呢?实际上在recyclerview.adapter中有两个onbindviewholder方法,一个是我们必须要重写的,而另一个的第三个参数就是一个payload的列表:

   @override
   public void onbindviewholder(recyclerview.viewholder holder, int position, list payloads) {}

所以我们只需在adapter中重写这个方法,如果list为空,执行原来的onbindviewholder进行整个item的更新,否则根据payloads的内容进行局部刷新:

@override
  public void onbindviewholder(recyclerview.viewholder holder, int position, list payloads) {
   if (payloads.isempty()) {
    onbindviewholder(holder, position);
   } else {
    myviewholder myviewholder = (myviewholder) holder;
    bundle bundle = (bundle) payloads.get(0);
    if (bundle.getstring(name_key) != null) {
     myviewholder.name.settext(bundle.getstring(name_key));
     myviewholder.name.settextcolor(color.blue);
    }
   }
  }

这里的payloads不会为null,所以直接判断是否为空即可。

这里注意:如果recyclerview中加载了大量数据,那么算法可能不会马上完成,要注意anr的问题,可以开启单独的线程进行计算。

总结

android中diffutil的使用就介绍到这了,希望这篇文章能对android开发者们有所帮助,如果有疑问大家可以留言交流。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网