1. 程式人生 > >Android新的工具類DiffUtil,RecyclerView的進化使用

Android新的工具類DiffUtil,RecyclerView的進化使用

跟作者的幾次溝通後發現作者非常的貼心,對了,該類不是7.0才能用,其相容到SDK 7,屬於support-v7中的類。還有該類的思想,有興趣可以應用到ListView中,如果你非常喜歡ListView的話。

不知道大家今天要不要上班,反正我是要上班了~祝大家和自己工作愉快

恩,我的御用作圖妹子竟然還在休假,還是沒帶電腦那種,所以今天的封面圖就湊合了~~

1

概述

DiffUtil是support-v7:24.2.0中的新工具類,它用來比較兩個資料集,尋找出舊資料集-》新資料集的最小變化量。

說到資料集,相信大家知道它是和誰相關的了,就是我的最愛,RecyclerView。

就我使用的這幾天來看,它最大的用處就是在RecyclerView重新整理時,不再無腦mAdapter.notifyDataSetChanged()。

以前無腦mAdapter.notifyDataSetChanged()有兩個缺點:

不會觸發RecyclerView的動畫(刪除、新增、位移、change動畫)

效能較低,畢竟是無腦的重新整理了一遍整個RecyclerView , 極端情況下:新老資料集一模一樣,效率是最低的。

使用DiffUtil後,改為如下程式碼:

DiffUtil.DiffResult diffResult =
DiffUtil.calculateDiff(new DiffCallBack(mDatas, newDatas), true);
diffResult.dispatchUpdatesTo(mAdapter);
它會自動計算新老資料集的差異,並根據差異情況,自動呼叫以下四個方法

adapter.notifyItemRangeInserted(position, count);
adapter.notifyItemRangeRemoved(position, count);
adapter.notifyItemMoved(fromPosition, toPosition);
adapter.notifyItemRangeChanged(position, count, payload);
顯然,這個四個方法在執行時都是伴有RecyclerView的動畫的,且都是定向重新整理方法,重新整理效率蹭蹭的上升了。

老規矩,先上圖,

圖一是無腦mAdapter.notifyDataSetChanged()的效果圖,可以看到重新整理互動很生硬,Item突然的出現在某個位置:

圖二是使用DiffUtils的效果圖,最明顯的是有插入、移動Item的動畫:

轉成GIF有些渣,下載文末Demo執行效果更佳哦。

本文將包含且不僅包含以下內容:

1 先介紹DiffUtil的簡單用法,實現重新整理時的“增量更新”效果。(“增量更新”是我自己的叫法)

2 DiffUtil的高階用法,在某項Item只有內容(data)變化,位置(position)未變化時,完成部分更新(官方稱之為Partial bind,部分繫結)。

3 瞭解到 RecyclerView.Adapter還有public void onBindViewHolder(VH holder, int position, List payloads)方法,並掌握它。

4 在子執行緒中計算DiffResult,在主執行緒中重新整理RecyclerView。

5 少部分人不喜歡的notifyItemChanged()導致Item白光一閃的動畫 如何去除。

6 DiffUtil部分類、方法 官方註釋的漢化

2

DiffUtil的簡單用法

前文也提到,DiffUtil是幫助我們在重新整理RecyclerView時,計算新老資料集的差異,並自動呼叫RecyclerView.Adapter的重新整理方法,以完成高效重新整理並伴有Item動畫的效果。

這裡需要讀者腦補一個簡單的RecyclerView展示列表的程式碼,當然還有個按鈕點選模擬重新整理。

ok,腦補成功,繼續…

下面開始進入正題,簡單使用DiffUtil,我們需要且僅需要額外編寫一個類。

想成為文藝青年,我們需要實現一個繼承自DiffUtil.Callback的類,實現它的四個abstract方法。

雖然這個類叫Callback,但是把它理解成:定義了一些用來比較新老Item是否相等的契約(Contract)、規則(Rule)的類, 更合適。

DiffUtil.Callback抽象類如下:

本Demo如下實現DiffUtil.Callback,核心方法配有中英雙語註釋(說人話就是,翻譯了官方的英文註釋,方便大家更好理解)。

註釋張寫了這麼詳細的註釋+簡單的程式碼,相信一眼可懂。
然後在使用時,註釋掉你以前寫的notifyDatasetChanged()方法吧,替換成以下程式碼:

講解:

步驟一
在將newDatas 設定給Adapter之前,先呼叫DiffUtil.calculateDiff()方法,計算出新老資料集轉化的最小更新集,就是DiffUtil.DiffResult物件。
DiffUtil.calculateDiff()方法定義如下:
第一個引數是DiffUtil.Callback物件,
第二個引數代表是否檢測Item的移動,改為false演算法效率更高,按需設定,我們這裡是true。

public static DiffResult calculateDiff(Callback cb, boolean detectMoves)
步驟二

然後利用DiffUtil.DiffResult物件的dispatchUpdatesTo()方法,傳入RecyclerView的Adapter,替代普通青年才用的mAdapter.notifyDataSetChanged()方法。
檢視原始碼可知,該方法內部,就是根據情況呼叫了adapter的四大定向重新整理方法。

小結:

所以說,DiffUtil不僅僅只能和RecyclerView配合,我們也可以自己實現ListUpdateCallback介面的四個方法去做一些事情。(我暫時不負責任隨便一想,想到可以配合自己專案裡的九宮格控制元件?或者優化我上篇文章寫的NestFullListView?小安利,見 ListView、RecyclerView、ScrollView裡巢狀ListView 相對優雅的解決方案:http://blog.csdn.net/zxt0601/article/details/52494665

至此,我們已進化成文藝青年,執行效果和第一節圖二基本一致,
唯一不同的是此時adapter.notifyItemRangeChanged()會有Item白光一閃的更新動畫 (本文Demo的postion為0的item)。 這個Item一閃的動畫有人喜歡有人恨,不過都不重要了,。

因為當我們學會了第三節的DiffUtil搞基(高階…鴻洋注)用法,你愛不愛這個ItemChange動畫,它都將隨風而去。(不知道是不是官方bug)

效果就是第一節的圖二,我們的item0其實圖片和文字都變化了,但是這個改變並沒有伴隨任何動畫。

讓我們邁向 文藝青年中的文藝青年 之路。

3

DiffUtil的高階用法

理論:
高階用法只涉及到兩個方法,
我們需要分別實現DiffUtil.Callback的
public Object getChangePayload(int oldItemPosition, int newItemPosition)方法,
返回的Object就是表示Item改變了哪些內容。

再配合RecyclerView.Adapter的
public void onBindViewHolder(VH holder, int position, List payloads)方法,
完成定向重新整理。(成為文青中的文青,文青青。)

敲黑板,這是一個新方法,注意它有三個引數,前兩個我們熟,第三個引數就包含了我們在getChangePayload()返回的Object。

好吧,那我們就先看看這個方法是何方神聖:
在v7-24.2.0的原始碼裡,它長這個樣子:

public void onBindViewHolder(VH holder, int position,
List payloads) {
onBindViewHolder(holder, position);
}
原來它內部就僅僅呼叫了兩個引數的onBindViewHolder(holder, position) ,(題外話,哎喲喂,我的NestFullListView 的Adapter也有幾分神似這種寫法,看來我離Google大神又近了一步)

看到這我才明白,其實onBind的入口,就是這個方法,它才是和onCreateViewHolder對應的方法,

原始碼往下翻幾行可以看到有個public final void bindViewHolder(VH holder, int position),它內部呼叫了三參的onBindViewHolder。

關於RecyclerView.Adapter 也不是三言兩句說的清楚的。(其實我只掌握到這裡)

好了不再跑題,回到我們的三引數的onBindViewHolder(VH holder, int position, List payloads),這個方法頭部有一大堆英文註釋,我一直覺得閱讀這些英文註釋對理解方法很有用處,於是我翻譯了一下,

翻譯:

由RecyclerView呼叫 用來在在指定的位置顯示資料。
這個方法應該更新ViewHolder裡的ItemView的內容,以反映在給定的位置 Item(的變化)。
請注意,不像ListView,如果給定位置的item的資料集變化了,RecyclerView不會再次呼叫這個方法,除非item本身失效了(invalidated ) 或者新的位置不能確定。
出於這個原因,在這個方法裡,你應該只使用 postion引數 去獲取相關的資料item,而且不應該去保持 這個資料item的副本。
如果你稍後需要這個item的position,例如設定clickListener。應該使用 ViewHolder.getAdapterPosition(),它能提供 更新後的位置。
(二筆的我看到這裡發現 這是在講解兩參的onbindViewHolder方法
下面是這個三參方法的獨特部分:)

**部分(partial)繫結**vs完整(full)繫結

payloads 引數 是一個從(notifyItemChanged(int, Object)或notifyItemRangeChanged(int, int, Object))裡得到的合併list。

如果payloads list 不為空,那麼當前綁定了舊資料的ViewHolder 和Adapter, 可以使用 payload的資料進行一次 高效的部分更新。
如果payload 是空的,Adapter必須進行一次完整繫結(呼叫兩參方法)。

Adapter不應該假定(想當然的認為) 在那些notifyxxxx通知方法傳遞過來的payload, 一定會在 onBindViewHolder()方法裡收到。(這一句翻譯不好 QAQ 看舉例就好)
舉例來說,當View沒有attached 在螢幕上時,這個來自notifyItemChange()的payload 就簡單的丟掉好了。

payloads物件不會為null,但是它可能是空(empty),這時候需要完整繫結(所以我們在方法裡只要判斷isEmpty就好,不用重複判空)。

作者語:這方法是一個高效的方法。 我是個低效的翻譯者,我看了40+分鐘。才終於明白,重要的部分已經加粗顯示。

實戰:

說了這麼多話,其實用起來超級簡單:
先看如何使用getChangePayload()方法,又附帶了中英雙語註釋

簡單的說,這個方法返回一個Object型別的payload,它包含了某個item的變化了的那些內容。
我們這裡使用Bundle儲存這些變化。

在Adapter裡如下重寫三參的onBindViewHolder:

這裡傳遞過來的payloads是一個List,由註釋可知,一定不為null,所以我們判斷是否是empty,
如果是empty,就呼叫兩參的函式,進行一次Full Bind。
如果不是empty,就進行partial bind,
通過下標0取出我們在getChangePayload方法裡返回的payload,然後遍歷payload的key,根據key檢索,如果payload裡攜帶有相應的改變,就取出來 然後更新在ItemView上。
(這裡,通過mDatas獲得的也是最新資料來源的資料,所以用payload的資料或者新資料的資料 進行更新都可以)
至此,我們已經掌握了重新整理RecyclerView,文藝青年中最文藝的那種寫法。

4
總結和其他

1 其實本文程式碼量很少,可下載Demo檢視,一共就四個類。

但是不知不覺又被我寫的這麼長,主要涉及到了一些原始碼的註釋的翻譯,方便大家更好的理解。

2 DiffUtil很適合下拉重新整理這種場景, 更新的效率提高了,而且帶動畫,而且~還不用你動腦子算了。 不過若是就做個刪除 點贊這種,完全不用DiffUtils。自己記好postion,判斷一下postion在不在螢幕裡,呼叫那幾個定向重新整理的方法即可。

3 其實DiffUtil不是隻能和RecyclerView.Adapter配合使用, 我們可以自己實現 ListUpdateCallback介面,利用DIffUtil幫我們找到新舊資料集的最小差異集 來做更多的事情。

4 注意 寫DEMO的時候,用於比較的新老資料集,不僅ArrayList不同,裡面每個data也要不同。 否則changed 無法觸發。 實際專案中遇不到,因為新資料往往是網路來的。

5 今天是中秋節的最後一天,我們公司居然就開始上班了!!!氣憤之餘,我怒碼一篇DiffUtil,我都不需要用DiffUtil,也能輕易比較出我們公司和其他公司的差異。QAQ,而且今天狀態不佳,居然寫了8個小時才完工。本以為這篇文章是可以入選微作文集的,沒想到也是蠻長的。沒有耐心的其實可以下載DEMO看看,程式碼量沒多少,使用起來還是很輕鬆的。

6 關於“白光一閃”onChange動畫, public Object getChangePayload() 這個方法返回不為null的話,onChange採用Partial bind,就不會出現。 反之就有。

相關推薦

Android工具DiffUtilRecyclerView進化使用

跟作者的幾次溝通後發現作者非常的貼心,對了,該類不是7.0才能用,其相容到SDK 7,屬於support-v7中的類。還有該類的思想,有興趣可以應用到ListView中,如果你非常喜歡ListView的話。 不知道大家今天要不要上班,反正我是要上班了~祝大

support-v7:24.2.0中的工具DiffUtil的使用方法

本文轉載自:http://blog.csdn.net/zxt0601/article/details/52562770 一 概述 DiffUtil是support-v7:24.2.0中的新工具類,它用來比較兩個資料集,尋找出舊資料集-》新資料集的最小變化量

Android SharedPreferences工具 實現List/Map的儲存讀取

最近因為需要將List集合和Map集合的資料儲存到SharedPreferences中,所以對以前自己封裝的SharedPreferencesUtil進行了修改,在原有的儲存讀Integer,String,Float,Long,Boolean,Object的基礎上增加了儲存讀取List<Obj

Android時間工具 本地轉UTCUTC轉本地

package com.peopleapp.en.util; import android.content.Context; import android.text.TextUtils; import android.text.format.DateFor

輕鬆把玩HttpClient之封裝HttpClient工具(九)增多檔案上傳功能

       在Git上有人給我提Issue,說怎麼上傳檔案,其實我一開始就想上這個功能,不過這半年比較忙,所以一直耽擱了。這次正好沒什麼任務了,趕緊完成這個功能。畢竟作為一款工具類,有基本的請求和下載功能,就差上傳了,有點說不過去。好了,天不早了,咱乾點正事吧。     

Android常用工具

cti def air exp -a src 重復元素 nec empty 主要介紹總結的Android開發中常用的工具類,大部分同樣適用於Java。目前包括HttpUtils、DownloadManagerPro、ShellUtils、PackageUtils、Prefe

嘗試造了個工具名為 Diana

叠代 引用 type eof 靜態 註意 form process 默認 項目地址: diana 文檔地址: http://muyunyun.cn/diana/ 造輪子的意義 為啥已經有如此多的前端工具類庫還要自己造輪子呢?個人認為有以下幾個觀點吧: 定制性強,能

Android Toast 工具

android  中常用系統吐司工具類 package cn.yhq.utils; import android.content.Context; import android.widget.Toast; /** * Created by hanbao on 2018/9/22. *

Android Sqlite 工具封裝

鑑於經常使用 Sqlite 資料庫做資料持久化處理,進行了一點封裝,方便使用。 該封裝類主要支援一下功能 支援多使用者資料儲存 支援 Sqlite資料庫升級 支援傳入 Sql 語句建表 支援 SQLiteDatabase 基本操作。比如:execSQL、ra

Android常用工具集合(持續更新)

1.訊息通知管理類,適配Android8.0 https://blog.csdn.net/huangliniqng/article/details/83537119 2.Android撥打電話工具類:  https://blog.csdn.net/huangliniqng/

文字校驗的工具--中文電話號碼郵箱身份證等資訊的校驗

這是專案中常用的文字校驗的工具類,包括:中文,電話號碼,郵箱,身份證等資訊的校驗 import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; impor

Android列印工具

Android列印除錯類,可打印出日誌所在的檔名、方法名、行號,使用的時候將Log換成LogTools即可,其他地方不變。 public class LogTools { private static final String DEBUG_TAG = "TEST";

Android日誌列印LogUtils能夠定位到方法名以及出現錯誤的行數並儲存日誌檔案

  關注finddreams,一起分享,一起進步!http://blog.csdn.net/finddreams/article/details/4556

時間工具DateUlits判斷某一時間距離當前時間幾分鐘、幾小時、幾天前

在我們開發中經常要對時間進行處理,把這些處理方法做成一個工具類是十分方面的,下面是我整理的一些時間處理的方法。 DateUlits方法目錄: 1、列印當前日期 2、輸入年-月-日  轉化為date型別 3、獲得一個 Date 物件例項 4、設定時間 5、獲取當前時間的前一

Android除錯工具模板UI應用內懸浮窗動態顯示記憶體佔用

EnDebugBubble 一個良好的Debug工具入口,是每個app所必備的,對於凌亂的除錯工具整理和除錯功能的統一使用方法,需要一個外觀整潔,內部具體的顯示容器。 傳統方案 方案一:一般的除錯工

android聯網工具

 聯網工具類 1.判斷網路是否可用  2.判斷網路是否已連線  3.獲取網路型別 4.判斷是否是WiFi網路   5. 判斷是否是Mobile網路 // 判斷網路是否可用  public static boolean isNetworkAvailable(Contex

Android-GsonUtil-工具

GsonUtil-工具類 是把Google提供的Gons進行了方法封裝,提供了關於一些常用的Gons使用的公共方法;   package common.library.utils; import android.text.TextUtils; import com.google.gso

Android-DateTimeAndroidUtil-工具

DateTimeAndroidUtil-工具類 是關於時間日前相關的公用方法;   package liudeli.mynetwork01.utils; import android.util.Log; import java.text.ParseException; import j

Android-SPUtil-工具

SPUtil-工具類 是專門對 Android共享首選項 SharedPreferences 的資料儲存/資料獲取,提供了公共的方法行為;   package common.library.utils; import android.content.Context; impor

Android-FileUtils-工具

FileUtils-工具類 是對檔案操作處理的方法進行了封裝,提供了公共的方法;   package common.library.utils; import android.annotation.SuppressLint; import android.net.Uri; import