版權宣告:
本賬號釋出文章均來自公眾號,承香墨影(cxmyDev),版權歸承香墨影所有。
每週會統一更新到這裡,如果喜歡,可關注公眾號獲取最新文章。
未經允許,不得轉載。
一、前言
DIffUtils 是 Support-v7:24:2.0 中,更新的工具類。因為已經更新了一段時間了,也不好說是最新更新的。
它主要是為了配合 RecyclerView 使用,通過比對新、舊兩個資料集的差異,生成舊資料到新資料的最小變動,然後對有變動的資料項,進行區域性重新整理。
接下來就 DiffUtil 的使用細節,進行一個詳細的講解,希望一篇文章就完全理解 DiffUtil。
二、為什麼會有DiffUtil
RecyclerView 自從被髮布以來,一直被說成是 ListView、GridView 等一系列列表控制元件的完美替代品。並且它本身使用起來也非常的好用,佈局切換方便、自帶 ViewHolder 、區域性更新並且可帶更新動畫等等。
區域性更新、並且可以很方便的設定更新動畫這一點,是 RecyclerView 一個不錯的亮點。它為此提供了對應的方法:
- adapter.notifyItemChange()
- adapter.notifyItemInserted()
- adapter.notifyItemRemoved()
- adapter.notifyItemMoved();
以上方法都是為了對資料集中,單一項進行操作,並且為了操作連續的資料集的變動,還提供了對應的 notifyRangeXxx()
方法。
雖然 RecyclerView 提供的區域性更新的方法,看似非常的好用,但是實際上,其實並沒有什麼用。
在實際開發中,最方便的做法就是無腦呼叫 notifyDataSetChanged()
,用於更新 Adapter 的資料集。
雖然 notifyDataSetChanged()
有一些缺點:
- 不會觸發 RecyclerView 的區域性更新的動畫。
- 效能低,會重新整理整個 RecyclerView 可視區域。
但是真有需要頻繁重新整理,前後兩個資料集的場景。
方案一:使用一個 notifyDataSetChanged()
方法。
方案二:自己寫一個資料集比對方法,然後去計算他們的差值,最後呼叫對應的方法更新到 RecyclerView 中去。
我這麼懶,如果不是必要,當然是會選 方案一 了。畢竟和之前 ListView 的時候,也沒有更差了。
Google 顯然也發現了這個問題,所以 DiffUtil 被髮布了。
三、介紹DiffUtil
就像前面說的,DiffUtil 就是為了解決這個痛點的。它能很方便的對兩個資料集之間進行比對,然後計算出變動情況,配合 RecyclerView.Adapter ,可以自動根據變動情況,呼叫 Adapter 的對應方法。
當然,DiffUtil 不僅只能配合 RecyclerView 使用,它實際上可以單獨用於比對兩個資料集,然後如何操作是可以定製的,那麼在什麼場景下使用,就全憑我們自己發揮了。
DiffUtil 在使用起來,主要需要關注幾個類:
- DiffUtil.Callback:具體用於限定資料集比對規則。
- DiffUtil.DiffResult:比對資料集之後,返回的差異結果。
1、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()
方法的。
2、DiffUtil.DiffResult
DiffUtil.DiffResult 其實就是 DiffUtil 通過 DiffUtil.Callback 計算出來,兩個資料集的差異。它是可以直接使用在 RecyclerView 上的。如果有必要,也是可以通過實現 ListUpdateCallback 介面,來比對這些差異的。
3、使用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 的對應方法。
四、上例子
既然已經說清楚了,那麼我們開始上例子了。
功能很簡單,有四個資料集,使用 RecyclerView 承載,然後有一個按鈕,用於輪換的切換資料集。
1、實現 DiffUtil.Callback
為了簡單,RecyclerView 中使用單一 ViewType ,並且使用一個 TextView 承載一個 字串來顯示。
那麼我們開始實現 Callback:
2、切換資料集
既然已經有了 DiffUtil.Callback 的實現之後,我們就需要對切換資料集的點選事件進行處理了。
3、實現效果
關鍵程式碼已經貼出來了,其實非常的簡單,最終執行的效果如下:
五、DiffUtil 效率問題
既然 DiffUtil 非常的好用,並且內部也實現了一套演算法,但是我們也需要關心它的效率問題。
根據 Google 官方文件中給出的例子,在 Nexus 5X M 系統上,DiffUtil 的效率問題,給出了一些參考的資料:
可以看到,實際上,DiffUtil 的演算法把效率問題解決的非常的好。在開啟計算移動的情況下,1000 條資料中有 200 個修改,平均值也只有 13.54 ms ,基本上都是毫秒級的。
Google 官方同時也指出,如果是對大資料集的比對,最好是方在子執行緒中去完成計算,也就是其實是存在堵塞 UI 的情況的。所以如果你遇見了使用 DiffUtil 之後,每次重新整理有卡頓的情況,可以考慮是否資料集太大,是否應該在子執行緒中完成計算。
六、結語
DiffUtil 已經介紹完了,如果覺得本文對你有幫助。都看到這裡了,點個贊再走吧。