版權聲明:算法
本帳號發佈文章均來自公衆號,承香墨影(cxmyDev),版權歸承香墨影全部。bash
每週會統一更新到這裏,若是喜歡,可關注公衆號獲取最新文章。數據結構
未經容許,不得轉載。工具
DIffUtils 是 Support-v7:24:2.0 中,更新的工具類。由於已經更新了一段時間了,也很差說是最新更新的。佈局
它主要是爲了配合 RecyclerView 使用,經過比對新、舊兩個數據集的差別,生成舊數據到新數據的最小變更,而後對有變更的數據項,進行局部刷新。性能
接下來就 DiffUtil 的使用細節,進行一個詳細的講解,但願一篇文章就徹底理解 DiffUtil。大數據
RecyclerView 自從被髮布以來,一直被說成是 ListView、GridView 等一系列列表控件的完美替代品。而且它自己使用起來也很是的好用,佈局切換方便、自帶 ViewHolder 、局部更新而且可帶更新動畫等等。動畫
局部更新、而且能夠很方便的設置更新動畫這一點,是 RecyclerView 一個不錯的亮點。它爲此提供了對應的方法:ui
以上方法都是爲了對數據集中,單一項進行操做,而且爲了操做連續的數據集的變更,還提供了對應的 notifyRangeXxx()
方法。spa
雖然 RecyclerView 提供的局部更新的方法,看似很是的好用,可是實際上,其實並無什麼用。
在實際開發中,最方便的作法就是無腦調用 notifyDataSetChanged()
,用於更新 Adapter 的數據集。
雖然 notifyDataSetChanged()
有一些缺點:
可是真有須要頻繁刷新,先後兩個數據集的場景。
方案一:使用一個 notifyDataSetChanged()
方法。
方案二:本身寫一個數據集比對方法,而後去計算他們的差值,最後調用對應的方法更新到 RecyclerView 中去。
我這麼懶,若是不是必要,固然是會選 方案一 了。畢竟和以前 ListView 的時候,也沒有更差了。
Google 顯然也發現了這個問題,因此 DiffUtil 被髮布了。
就像前面說的,DiffUtil 就是爲了解決這個痛點的。它能很方便的對兩個數據集之間進行比對,而後計算出變更狀況,配合 RecyclerView.Adapter ,能夠自動根據變更狀況,調用 Adapter 的對應方法。
固然,DiffUtil 不只只能配合 RecyclerView 使用,它實際上能夠單獨用於比對兩個數據集,而後如何操做是能夠定製的,那麼在什麼場景下使用,就全憑咱們本身發揮了。
DiffUtil 在使用起來,主要須要關注幾個類:
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 經過 DiffUtil.Callback 計算出來,兩個數據集的差別。它是能夠直接使用在 RecyclerView 上的。若是有必要,也是能夠經過實現 ListUpdateCallback 接口,來比對這些差別的。
介紹了 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 的對應方法。
既然已經說清楚了,那麼咱們開始上例子了。
功能很簡單,有四個數據集,使用 RecyclerView 承載,而後有一個按鈕,用於輪換的切換數據集。
爲了簡單,RecyclerView 中使用單一 ViewType ,而且使用一個 TextView 承載一個 字符串來顯示。
那麼咱們開始實現 Callback:
既然已經有了 DiffUtil.Callback 的實現以後,咱們就須要對切換數據集的點擊事件進行處理了。
關鍵代碼已經貼出來了,其實很是的簡單,最終運行的效果以下:
既然 DiffUtil 很是的好用,而且內部也實現了一套算法,可是咱們也須要關心它的效率問題。
根據 Google 官方文檔中給出的例子,在 Nexus 5X M 系統上,DiffUtil 的效率問題,給出了一些參考的數據:
能夠看到,實際上,DiffUtil 的算法把效率問題解決的很是的好。在開啓計算移動的狀況下,1000 條數據中有 200 個修改,平均值也只有 13.54 ms ,基本上都是毫秒級的。
Google 官方同時也指出,若是是對大數據集的比對,最好是方在子線程中去完成計算,也就是實際上是存在堵塞 UI 的狀況的。因此若是你碰見了使用 DiffUtil 以後,每次刷新有卡頓的狀況,能夠考慮是否數據集太大,是否應該在子線程中完成計算。
DiffUtil 已經介紹完了,若是以爲本文對你有幫助。都看到這裏了,點個贊再走吧。