成都android培训
达内成都高升桥中心

18684047202

热门课程

Android:DiffUtil使用教程

  • 时间:2016-10-10
  • 发布:达内
  • 来源:达内

谷歌最近更新了Support Library 24.2.0,而DiffUtil就是在这个版本添加的一个工具类。DiffUtil是一个查找集合变化的工具类,是搭配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对象:

1 class Student {

2 private String name;

3 private int num;

4

5 public Student(String name, int num) {

6 this.name = name;

7 this.num = num;

8 }

9

10 public String getName() {

11 return name;

12 }

13

14 public void setName(String name) {

15 this.name = name;

16 }

17

18 public int getNum() {

19 return num;

20 }

21

22 public void setNum(int num) {

23 this.num = num;

24 }

25 }

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

1 class MyAdapter extends RecyclerView.Adapter {

2 private ArrayList data;

3

4 ArrayList getData() {

5 return data;

6 }

7

8 void setData(ArrayList data) {

9 this.data = new ArrayList<>(data);

10 }

11

12 @Override

13 public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

14 View itemView = LayoutInflater.from(RecyclerViewActivity.this).inflate(R.layout.itemview, null);

15 return new MyViewHolder(itemView);

16 }

17

18 @Override

19 public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

20 MyViewHolder myViewHolder = (MyViewHolder) holder;

21 Student student = data.get(position);

22 myViewHolder.tv.setText(student.getNum() + "." + student.getName());

23 }

24

25 @Override

26 public int getItemCount() {

27 return data.size();

28 }

29

30 class MyViewHolder extends RecyclerView.ViewHolder {

31 TextView tv;

32

33 MyViewHolder(View itemView) {

34 super(itemView);

35 tv = (TextView) itemView.findViewById(R.id.item_tv);

36 }

37 }

38 }

初始化数据集合:

1 private void initData() {

2 students = new ArrayList<>();

3 Student s1 = new Student("John", 1);

4 Student s2 = new Student("Curry", 2);

5 Student s3 = new Student("Rose", 3);

6 Student s4 = new Student("Dante", 4);

7 Student s5 = new Student("Lunar", 5);

8 students.add(s1);

9 students.add(s2);

10 students.add(s3);

11 students.add(s4);

12 students.add(s5);

13 }

接着实例化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的定义:

1 class MyAdapter extends RecyclerView.Adapter {

2 private ArrayList data;

3

4 ArrayList getData() {

5 return data;

6 }

7

8 void setData(ArrayList data) {

9 this.data = new ArrayList<>(data);

10 }

11

12 // 省略部分代码

13 ......

14 }

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

DiffUtil的使用方法:

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

1 public void change(View view) {

2 students.set(1, new Student("Fndroid", 2));

3 students.add(new Student("Jason", 8));

4 Student s2 = students.get(2);

5 students.remove(2);

6 students.add(s2);

7

8 ArrayList old_students = adapter.getData();

9 DiffUtil.DiffResult result = DiffUtil.calculateDiff(new MyCallback(old_students, students), true);

10 adapter.setData(students);

11 result.dispatchUpdatesTo(adapter);

12 }

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接口的实现类如何定义:

1 private class MyCallback extends DiffUtil.Callback {

2 private ArrayList old_students, new_students;

3

4 MyCallback(ArrayList data, ArrayList students) {

5 this.old_students = data;

6 this.new_students = students;

7 }

8

9 @Override

10 public int getOldListSize() {

11 return old_students.size();

12 }

13

14 @Override

15 public int getNewListSize() {

16 return new_students.size();

17 }

18

19 // 判断Item是否已经存在

20 @Override

21 public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {

22 return old_students.get(oldItemPosition).getNum() == new_students.get(newItemPosition).getNum();

23 }

24

25 // 如果Item已经存在则会调用此方法,判断Item的内容是否一致

26 @Override

27 public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {

28 return old_students.get(oldItemPosition).getName().equals(new_students.get(newItemPosition).getName());

29 }

30 }

这里根据学号判断是否同一个Item,根据姓名判断这个Item是否有被修改。

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


上一篇:Android 7.0 Nougat正式版终于上线!
下一篇:Android:谷歌正式发布安卓7.0

高中生求职难参加达内UI培训,华丽逆袭成为设计

郑大学生参加达内Java培训,成功入职亚信联创

达内与石家庄财经职业学院开展全方位合作,共育人才共赢未来

学生跨专业参加达内培训,获12k高薪入职中创汇科

选择城市和中心
贵州省

广西省

海南省