RecyclerView 配合 DiffUtil,好用到飛起


版權聲明:

本賬號發布文章均來自公眾號,承香墨影(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 的對應方法。

diff-dut

四、上例子

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

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

1、實現 DiffUtil.Callback

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

那么我們開始實現 Callback:

diff-callback

2、切換數據集

既然已經有了 DiffUtil.Callback 的實現之后,我們就需要對切換數據集的點擊事件進行處理了。

diff-change

3、實現效果

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

五、DiffUtil 效率問題

既然 DiffUtil 非常的好用,並且內部也實現了一套算法,但是我們也需要關心它的效率問題。

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

diff-duibi

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

Google 官方同時也指出,如果是對大數據集的比對,最好是方在子線程中去完成計算,也就是其實是存在堵塞 UI 的情況的。所以如果你遇見了使用 DiffUtil 之后,每次刷新有卡頓的情況,可以考慮是否數據集太大,是否應該在子線程中完成計算。

六、結語

DiffUtil 已經介紹完了,如果覺得本文對你有幫助。都看到這里了,點個贊再走吧。

公眾號二維碼.jpg


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM