Android開發學習之路-DiffUtil使用教程

來源:互聯網
上載者:User

標籤:

Google最新動向了Support Library 24.2.0,而DiffUtil就是在這個版本添加的一個工具類。

DiffUtil是一個尋找集合變化的工具類,是搭配RecyclerView一起使用的,如果你還不瞭解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對象:

 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<Student> data; 3  4         ArrayList<Student> getData() { 5             return data; 6         } 7  8         void setData(ArrayList<Student> data) { 9             this.data = new ArrayList<>(data);10         }11 12         @Override13         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         @Override19         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         @Override26         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<Student> data; 3  4         ArrayList<Student> getData() { 5             return data; 6         } 7  8         void setData(ArrayList<Student> 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<Student> 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<Student> old_students, new_students; 3  4         MyCallback(ArrayList<Student> data, ArrayList<Student> students) { 5             this.old_students = data; 6             this.new_students = students; 7         } 8  9         @Override10         public int getOldListSize() {11             return old_students.size();12         }13 14         @Override15         public int getNewListSize() {16             return new_students.size();17         }18 19         // 判斷Item是否已經存在20         @Override21         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         @Override27         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開發學習之路-DiffUtil使用教程

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.