国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Android > 正文

Android開發之DiffUtil的使用詳解

2019-12-12 05:12:57
字體:
來源:轉載
供稿:網友

寫在前面的話

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開發者們有所幫助,如果有疑問大家可以留言交流。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 盐源县| 惠来县| 准格尔旗| 建瓯市| 台湾省| 哈密市| 平乐县| 洪泽县| 商河县| 邵阳县| 林甸县| 永清县| 应城市| 綦江县| 英超| 公安县| 海丰县| 香港| 会泽县| 集贤县| 关岭| 长阳| 南投县| 阿坝县| 乐业县| 衢州市| 会宁县| 宾川县| 永和县| 定陶县| 武邑县| 县级市| 阿合奇县| 丹寨县| 承德县| 鄂托克旗| 闵行区| 台前县| 海林市| 建水县| 荃湾区|