RecyclerView 配合 DiffUtil,好用到飛起

版權聲明:算法

本帳號發佈文章均來自公衆號,承香墨影(cxmyDev),版權歸承香墨影全部。bash

每週會統一更新到這裏,若是喜歡,可關注公衆號獲取最新文章。數據結構

未經容許,不得轉載。工具

1、前言

DIffUtils 是 Support-v7:24:2.0 中,更新的工具類。由於已經更新了一段時間了,也很差說是最新更新的。佈局

它主要是爲了配合 RecyclerView 使用,經過比對新、舊兩個數據集的差別,生成舊數據到新數據的最小變更,而後對有變更的數據項,進行局部刷新。性能

接下來就 DiffUtil 的使用細節,進行一個詳細的講解,但願一篇文章就徹底理解 DiffUtil。大數據

2、爲何會有DiffUtil

RecyclerView 自從被髮布以來,一直被說成是 ListView、GridView 等一系列列表控件的完美替代品。而且它自己使用起來也很是的好用,佈局切換方便、自帶 ViewHolder 、局部更新而且可帶更新動畫等等。動畫

局部更新、而且能夠很方便的設置更新動畫這一點,是 RecyclerView 一個不錯的亮點。它爲此提供了對應的方法:ui

  • adapter.notifyItemChange()
  • adapter.notifyItemInserted()
  • adapter.notifyItemRemoved()
  • adapter.notifyItemMoved();

以上方法都是爲了對數據集中,單一項進行操做,而且爲了操做連續的數據集的變更,還提供了對應的 notifyRangeXxx() 方法。spa

雖然 RecyclerView 提供的局部更新的方法,看似很是的好用,可是實際上,其實並無什麼用。

在實際開發中,最方便的作法就是無腦調用 notifyDataSetChanged(),用於更新 Adapter 的數據集。

雖然 notifyDataSetChanged() 有一些缺點:

  • 不會觸發 RecyclerView 的局部更新的動畫。
  • 性能低,會刷新整個 RecyclerView 可視區域。

可是真有須要頻繁刷新,先後兩個數據集的場景。

方案一:使用一個 notifyDataSetChanged() 方法。

方案二:本身寫一個數據集比對方法,而後去計算他們的差值,最後調用對應的方法更新到 RecyclerView 中去。

我這麼懶,若是不是必要,固然是會選 方案一 了。畢竟和以前 ListView 的時候,也沒有更差了。

Google 顯然也發現了這個問題,因此 DiffUtil 被髮布了。

3、介紹DiffUtil

就像前面說的,DiffUtil 就是爲了解決這個痛點的。它能很方便的對兩個數據集之間進行比對,而後計算出變更狀況,配合 RecyclerView.Adapter ,能夠自動根據變更狀況,調用 Adapter 的對應方法。

固然,DiffUtil 不只只能配合 RecyclerView 使用,它實際上能夠單獨用於比對兩個數據集,而後如何操做是能夠定製的,那麼在什麼場景下使用,就全憑咱們本身發揮了。

DiffUtil 在使用起來,主要須要關注幾個類:

  • DiffUtil.Callback:具體用於限定數據集比對規則。
  • DiffUtil.DiffResult:比對數據集以後,返回的差別結果。

一、DiffUtil.Callback

DiffUtil.Callback 主要就是爲了限定兩個數據集中,子項的比對規則。畢竟開發者面對的數據結構多種多樣,既然無法作一套通用的內容比對方式,那麼就將比對的規則,交還給開發者來實現便可。

在 Callback 中,其實只須要實現 4 個方法:

  • getOldListSize():舊數據集的長度。
  • getNewListSize():新數據集的長度
  • areItemsTheSame():判斷是不是同一個Item。
  • areContentsTheSame():若是是通一個Item,此方法用於判斷是否同一個 Item 的內容也相同。

前兩個是獲取數據集長度的方法,這沒什麼好說的。可是後兩個方法,主要是爲了對應多佈局的狀況產生的,也就是存在多個 viewType 和多個 ViewHodler 的狀況。首先須要使用 areItemsTheSame() 方法比對是否來自同一個 viewType(也就是同一個 ViewHolder ) ,而後再經過 areContentsTheSame() 方法比對其內容是否也相等。

其實 Callback 還有一個 getChangePayload() 的方法,它能夠在 ViewType 相同,可是內容不相同的時候,用 payLoad 記錄須要在這個 ViewHolder 中,具體須要更新的View。

areItemsTheSame()areContentsTheSame()getChangePayload() 分別表明了不一樣量級的刷新。

首先會經過 areItemsTheSame() 判斷當前 position 下,ViewType 是否一致,若是不一致就代表當前 position 下,從數據到 UI 結構上所有變化了,那麼就不關心內容,直接更新就行了。若是一致的話,那麼其實 View 是能夠複用的,就還須要再經過 areContentsTheSame() 方法判斷其內容是否一致,若是一致,則表示是同一條數據,不須要作額外的操做。可是一旦不一致,則還會調用 getChangePayload() 來標記究竟是哪一個地方的不同,最終標記須要更新的地方,最終返回給 DiffResult 。

固然,對性能要是要求沒那麼高的狀況下,是能夠不使用 getChangedPayload() 方法的。

二、DiffUtil.DiffResult

DiffUtil.DiffResult 其實就是 DiffUtil 經過 DiffUtil.Callback 計算出來,兩個數據集的差別。它是能夠直接使用在 RecyclerView 上的。若是有必要,也是能夠經過實現 ListUpdateCallback 接口,來比對這些差別的。

三、使用DiffUtil

介紹了 Callback 和 DiffResult 以後,其實就能夠正常使用 DiffUtil 來進行數據集的比對了。

在這個過程當中,其實真的很簡單,只須要調用兩個方法:

DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallBack(oldDatas, newDatas), true);
diffResult.dispatchUpdatesTo(mAdapter);複製代碼

calculateDiff 方法主要是用於經過一個具體的 DiffUtils.Callback 實現對象,來計算出兩個數據集差別的結果,獲得 DiffUtil.DiffResult

而 calculateDiff 的另一個參數,用於標記是否須要檢測 Item 的移動。

DiffUtil 使用的是 Eugene Myers 的差異算法,這個算法自己是不檢查元素的移動的。也就是說,有元素的移動它也只是會先標記爲刪除,而後再標記插入。而若是須要計算元素的移動,它實際上也是在經過 Eugene Myers 算法比對以後,再進行一次移動檢查。因此,若是集合自己已經排序過了,能夠不進行移動的檢查。

dispatchUpdatesTo() 就是將這個數據集差別的結果,經過 Adapter 更新到 RecyclerView 上面。

實際上 dispatchUpdatesTo(Adapter) ,也是使用的 ListUpdateCallback 這個接口,在其中得到差別,而後調用 Adapter 的對應方法。

diff-dut
diff-dut

4、上例子

既然已經說清楚了,那麼咱們開始上例子了。

功能很簡單,有四個數據集,使用 RecyclerView 承載,而後有一個按鈕,用於輪換的切換數據集。

一、實現 DiffUtil.Callback

爲了簡單,RecyclerView 中使用單一 ViewType ,而且使用一個 TextView 承載一個 字符串來顯示。

那麼咱們開始實現 Callback:

diff-callback
diff-callback

二、切換數據集

既然已經有了 DiffUtil.Callback 的實現以後,咱們就須要對切換數據集的點擊事件進行處理了。

diff-change
diff-change

三、實現效果

關鍵代碼已經貼出來了,其實很是的簡單,最終運行的效果以下:

5、DiffUtil 效率問題

既然 DiffUtil 很是的好用,而且內部也實現了一套算法,可是咱們也須要關心它的效率問題。

根據 Google 官方文檔中給出的例子,在 Nexus 5X M 系統上,DiffUtil 的效率問題,給出了一些參考的數據:

diff-duibi
diff-duibi

能夠看到,實際上,DiffUtil 的算法把效率問題解決的很是的好。在開啓計算移動的狀況下,1000 條數據中有 200 個修改,平均值也只有 13.54 ms ,基本上都是毫秒級的。

Google 官方同時也指出,若是是對大數據集的比對,最好是方在子線程中去完成計算,也就是實際上是存在堵塞 UI 的狀況的。因此若是你碰見了使用 DiffUtil 以後,每次刷新有卡頓的狀況,能夠考慮是否數據集太大,是否應該在子線程中完成計算。

6、結語

DiffUtil 已經介紹完了,若是以爲本文對你有幫助。都看到這裏了,點個贊再走吧。

公衆號二維碼.jpg
公衆號二維碼.jpg
相關文章
相關標籤/搜索