欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android開發(fā)之DiffUtil的使用詳解

 更新時(shí)間:2016年09月20日 14:25:14   作者:Fndroid  
這篇文章文給大家介紹了DiffUtil的使用,相信大家每位Android開發(fā)者們都知道谷歌最近更新了Support Library 24.2.0,而DiffUtil就是在這個(gè)版本添加的一個(gè)工具類。下面就跟著小編一起來看看,有需要的可以參考借鑒。

寫在前面的話

DiffUtil是一個(gè)查找集合變化的工具類,是搭配RecyclerView一起使用的,如果你還不了解RecyclerView,可以閱讀一些資料,這里就不介紹了。

先放效果圖:

可以看到,當(dāng)我們點(diǎn)擊按鈕的時(shí)候,這個(gè)RecyclerView所顯示的集合發(fā)生了改變,有的元素被增加了(8.Jason),也有的元素被移動(dòng)了(3.Rose),甚至是被修改了(2.Fndroid)。

RecyclerView對(duì)于每個(gè)Item的動(dòng)畫是以不同方式刷新的:

     notifyItemInserted

     notifyItemChanged

     notifyItemMoved

     notifyItemRemoved

而對(duì)于連續(xù)的幾個(gè)Item的刷新,可以調(diào)用:

     notifyItemRangeChanged

     notifyItemRangeInserted

     notifyItemRangeRemoved

而由于集合發(fā)生變化的時(shí)候,只可以調(diào)用notifyDataSetChanged方法進(jìn)行整個(gè)界面的刷新,并不能根據(jù)集合的變化為每一個(gè)變化的元素添加動(dòng)畫。所以這里就有了DiffUtil來解決這個(gè)問題。

DiffUtil的作用,就是找出集合中每一個(gè)Item發(fā)生的變化,然后對(duì)每個(gè)變化給予對(duì)應(yīng)的刷新。

這個(gè)DiffUtil使用的是Eugene Myers的差別算法,這個(gè)算法本身不能檢查到元素的移動(dòng),也就是移動(dòng)只能被算作先刪除、再增加,而DiffUtil是在算法的結(jié)果后再進(jìn)行一次移動(dòng)檢查。假設(shè)在不檢測元素移動(dòng)的情況下,算法的時(shí)間復(fù)雜度為O(N + D2),而檢測元素移動(dòng)則復(fù)雜度為O(N2)。所以,如果集合本身就已經(jīng)排好序,可以不進(jìn)行移動(dòng)的檢測提升效率。 

下面我們一起來看看這個(gè)工具怎么用。

首先對(duì)于每個(gè)Item,數(shù)據(jù)是一個(gè)Student對(duì)象:

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);
   }
  }
 }

初始化數(shù)據(jù)集合:

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);
 }

接著實(shí)例化Adapter并設(shè)置給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);
 }

這些內(nèi)容都不是本篇的內(nèi)容,但是,需要注意到的一個(gè)地方是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的引用保存,而是重新的建立一個(gè)ArrayList,先記著,后面會(huì)解釋為什么要這樣做。

DiffUtil的使用方法:

當(dāng)鼠標(biāo)按下時(shí),修改ArrayList的內(nèi)容:

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行是對(duì)集合進(jìn)行修改,第8行先獲取到adapter中的集合為舊的數(shù)據(jù)。

重點(diǎn)看第9行調(diào)用DiffUtil.calculateDiff方法來計(jì)算集合的差別,這里要傳入一個(gè)CallBack接口的實(shí)現(xiàn)類(用于指定計(jì)算的規(guī)則)并且把新舊數(shù)據(jù)都傳遞給這個(gè)接口的實(shí)現(xiàn)類,最后還有一個(gè)boolean類型的參數(shù),這個(gè)參數(shù)指定是否需要進(jìn)行Move的檢測,如果不需要,如果有Item移動(dòng)了,會(huì)被認(rèn)為是先remove,然后insert。這里指定為true,所以就有了動(dòng)圖顯示的移動(dòng)效果。

第10行重新將新的數(shù)據(jù)設(shè)置給Adapter。

第11行調(diào)用第9行得到的DiffResult對(duì)象的dispatchUpdatesTo方法通知RecyclerView刷新對(duì)應(yīng)發(fā)生變化的Item。

這里回到上面說的setData方法,因?yàn)槲覀冊(cè)谶@里要區(qū)分兩個(gè)集合,如果在setData方法中直接保存引用,那么在2-6行的修改就直接修改了Adapter中的集合了(Java知識(shí))。

如果設(shè)置不檢查Item的移動(dòng),效果如下:

接著我們看看CallBack接口的實(shí)現(xiàn)類如何定義:

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是否已經(jīng)存在
  @Override
  public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
   return old_students.get(oldItemPosition).getNum() == new_students.get(newItemPosition).getNum();
  }

  // 如果Item已經(jīng)存在則會(huì)調(diào)用此方法,判斷Item的內(nèi)容是否一致
  @Override
  public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
   return old_students.get(oldItemPosition).getName().equals(new_students.get(newItemPosition).getName());
  }
 }

這里根據(jù)學(xué)號(hào)判斷是否同一個(gè)Item,根據(jù)姓名判斷這個(gè)Item是否有被修改。

實(shí)際上,這個(gè)Callback抽象類還有一個(gè)方法getChangePayload() ,這個(gè)方法的作用是我們可以通過這個(gè)方法告訴Adapter對(duì)這個(gè)Item進(jìn)行局部的更新而不是整個(gè)更新。

先要知道這個(gè)payload是什么?payload是一個(gè)用來描述Item變化的對(duì)象,也就是我們的Item發(fā)生了哪些變化,這些變化就封裝成一個(gè)payload,所以我們一般可以用Bundle來充當(dāng)。

接著,getChangePayload()方法是在areItemsTheSame()返回true,而areContentsTheSame()返回false時(shí)被回調(diào)的,也就是一個(gè)Item的內(nèi)容發(fā)生了變化,而這個(gè)變化有可能是局部的(例如微博的點(diǎn)贊,我們只需要刷新圖標(biāo)而不是整個(gè)Item)。所以可以在getChangePayload()中封裝一個(gè)Object來告訴RecyclerView進(jìn)行局部的刷新。

假設(shè)上例中學(xué)號(hào)和姓名用不同的TextView顯示,當(dāng)我們修改了一個(gè)學(xué)號(hào)對(duì)應(yīng)的姓名時(shí),局部刷新姓名即可(這里例子可能顯得比較多余,但是如果一個(gè)Item很復(fù)雜,用處就比較大了):

先是重寫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;
  }

返回的這個(gè)對(duì)象會(huì)在什么地方收到呢?實(shí)際上在RecyclerView.Adapter中有兩個(gè)onBindViewHolder方法,一個(gè)是我們必須要重寫的,而另一個(gè)的第三個(gè)參數(shù)就是一個(gè)payload的列表:

   @Override
   public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads) {}

所以我們只需在Adapter中重寫這個(gè)方法,如果List為空,執(zhí)行原來的onBindViewHolder進(jìn)行整個(gè)Item的更新,否則根據(jù)payloads的內(nèi)容進(jìn)行局部刷新:

@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不會(huì)為null,所以直接判斷是否為空即可。

這里注意:如果RecyclerView中加載了大量數(shù)據(jù),那么算法可能不會(huì)馬上完成,要注意ANR的問題,可以開啟單獨(dú)的線程進(jìn)行計(jì)算。

總結(jié)

Android中DiffUtil的使用就介紹到這了,希望這篇文章能對(duì)Android開發(fā)者們有所幫助,如果有疑問大家可以留言交流。

相關(guān)文章

  • Android SQLite3多線程操作問題研究總結(jié)

    Android SQLite3多線程操作問題研究總結(jié)

    這篇文章主要介紹了Android SQLite3多線程操作問題研究總結(jié),本文總結(jié)了SQLite3是否支持多線程、SQLiteDatabase的同步鎖、多線程讀數(shù)據(jù)庫等問題,需要的朋友可以參考下
    2015-03-03
  • Android仿即刻首頁垂直滾動(dòng)圖,炫酷到底!

    Android仿即刻首頁垂直滾動(dòng)圖,炫酷到底!

    這篇文章主要為大家詳細(xì)介紹了Android仿即刻首頁垂直滾動(dòng)圖效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-09-09
  • Android string.xml中的替換方法

    Android string.xml中的替換方法

    這篇文章主要介紹了Android string.xml中的替換方法,結(jié)合實(shí)例形式簡單分析了string.xml中變量替換的相關(guān)技巧,需要的朋友可以參考下
    2016-01-01
  • 詳解android系統(tǒng)的定制

    詳解android系統(tǒng)的定制

    這篇內(nèi)容給大家分享了關(guān)于android系統(tǒng)的定制的一些步驟和基本知識(shí)點(diǎn),有興趣的朋友參考學(xué)習(xí)下。
    2018-06-06
  • android模擬器開發(fā)和測試nfc應(yīng)用實(shí)例詳解

    android模擬器開發(fā)和測試nfc應(yīng)用實(shí)例詳解

    本文介紹android模擬器開發(fā)nfc應(yīng)用詳解,大家參考使用吧
    2013-12-12
  • 非常詳細(xì)的android so庫逆向調(diào)試教程

    非常詳細(xì)的android so庫逆向調(diào)試教程

    這篇文章主要給大家介紹了關(guān)于android so庫逆向調(diào)試的相關(guān)資料,文中通過示例代碼以及圖文介紹的非常詳細(xì),對(duì)各位Android開發(fā)者具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2021-08-08
  • Android 微信搖一搖功能實(shí)現(xiàn)詳細(xì)介紹

    Android 微信搖一搖功能實(shí)現(xiàn)詳細(xì)介紹

    這篇文章主要介紹了Android 微信搖一搖功能實(shí)現(xiàn)詳細(xì)介紹的相關(guān)資料,并附實(shí)例代碼及實(shí)現(xiàn)微信搖一搖的思路,需要的朋友可以參考下
    2016-11-11
  • 去掉ListView底部的ListDivider的方法

    去掉ListView底部的ListDivider的方法

    下面小編就為大家?guī)硪黄サ鬖istView底部的ListDivider的方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-04-04
  • Android Activity之間傳遞圖片(Bitmap)的方法

    Android Activity之間傳遞圖片(Bitmap)的方法

    這篇文章介紹了Android Activity之間傳遞圖片(Bitmap)的方法,有需要的朋友可以參考一下
    2013-08-08
  • Android中View位置和觸摸事件詳解

    Android中View位置和觸摸事件詳解

    這篇文章主要給大家介紹了關(guān)于Android中View位置和觸摸事件的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-08-08

最新評(píng)論