Android开发之DiffUtil的使用详解

Android开发之DiffUtil的使用详解,第1张

概述写在前面的话DiffUtil是一个查找集合变化的工具类,是搭配RecyclerView一起使用的,如果你还不了解RecyclerView,可以阅读一些资料,这里就不介绍了。

写在前面的话

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,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开发者们有所帮助,如果有疑问大家可以留言交流。

总结

以上是内存溢出为你收集整理的Android开发之DiffUtil的使用详解全部内容,希望文章能够帮你解决Android开发之DiffUtil的使用详解所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1148133.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-31
下一篇 2022-05-31

发表评论

登录后才能评论

评论列表(0条)

保存