1、前言
DIffUtils 是 Support-v7:24:2.0 中,更新的工具類。
它主要是為了配合 RecyclerView 使用,通過比對新、舊兩個數據集的差異,生成舊數據到新數據的最小變動,然后對有變動的數據項,進行局部刷新。
當然,DiffUtil 不僅只能配合 RecyclerView 使用,它實際上可以單獨用于比對兩個數據集,然后如何操作是可以定制的,那么在什么場景下使用,就全憑我們自己發揮了。
2、DiffUtil
DiffUtil 在使用起來,主要需要關注幾個類:
- DiffUtil.Callback:具體用于限定數據集比對規則。
- DiffUtil.DiffResult:比對數據集之后,返回的差異結果。
2.1、DiffUtil.Callback
DiffUtil.Callback 主要就是為了限定兩個數據集中,子項的比對規則。畢竟開發者面對的數據結構多種多樣,既然沒法做一套通用的內容比對方式,那么就將比對的規則,交還給開發者來實現即可。
在 Callback 中,其實只需要實現 4 個方法:
- getOldListSize():舊數據集的長度。
- getNewListSize():新數據集的長度
- areItemsTheSame():判斷是否是同一個Item。
- areContentsTheSame():如果是通一個Item,此方法用于判斷是否同一個 Item 的內容也相同。
前兩個是獲取數據集長度的方法,這沒什么好說的。但是后兩個方法,主要是為了對應多布局的情況產生的,也就是存在多個 viewType的情況。首先需要使用 areItemsTheSame()
方法比對是否來自同一個 viewType
,然后再通過 areContentsTheSame()
方法比對其內容是否也相等。
其實 Callback 還有一個 getChangePayload()
的方法,它可以在 ViewType 相同,但是內容不相同的時候
,用 payLoad 記錄需要在這個 ViewHolder 中,然后去更新具體的View。
areItemsTheSame()、areContentsTheSame()、getChangePayload()
分別代表了不同量級的刷新。
首先會通過
areItemsTheSame()
判斷當前 position 下的ViewType 是否一致,如果不一致就表明當前 position 下,從數據到 UI 結構上全部變化了,那么就不關心內容(areContentsTheSame()、getChangePayload()不會被調用
),直接更新就好了。如果一致的話,那么其實 View 是可以復用的,就還需要再通過areContentsTheSame()
方法判斷其內容是否一致,如果一致,則表示是同一條數據,不需要做額外的操作。但是一旦不一致,則還會調用getChangePayload()
來標記到底是哪個地方的不一樣,最終標記需要更新的地方,最終返回給 DiffResult 。
當然,對性能要是要求沒那么高的情況下,是可以不使用 getChangedPayload()
方法的。
2.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 的移動。
而 dispatchUpdatesTo()
就是將這個數據集差異的結果,通過 Adapter 更新到 RecyclerView 上面。
實際上 dispatchUpdatesTo(Adapter) ,也是使用的 ListUpdateCallback 這個接口,在其中獲得差異,然后調用 Adapter 的對應方法。
public void dispatchUpdatesTo(final RecyclerView.Adapter adapter) {
dispatchUpdatesTo(new ListUpdateCallback() {
@Override
public void onInserted(int position, int count) {
adapter.notifyItemRangeInserted(position, count);
}
@Override
public void onRemoved(int position, int count) {
adapter.notifyItemRangeRemoved(position, count);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
adapter.notifyItemMoved(fromPosition, toPosition);
}
@Override
public void onChanged(int position, int count, Object payload) {
adapter.notifyItemRangeChanged(position, count, payload);
}
});
}
4、實例
既然已經說清楚了,那么我們開始上例子了。
功能很簡單,有四個數據集,使用 RecyclerView 承載,然后有一個按鈕,用于輪換的切換數據集。
4.1、實現 DiffUtil.Callback
public class AdapterDiffCallBack extends DiffUtil.Callback {
private ArrayList<Bean> oldData;
private ArrayList<Bean> newData;
public AdapterDiffCallBack(ArrayList<Bean> oldData, ArrayList<Bean> newData) {
this.oldData = oldData;
this.newData = newData;
}
@Override
public int getOldListSize() {
return oldData.size();
}
@Override
public int getNewListSize() {
return newData.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return oldData.get(oldItemPosition).getType() == newData.get(newItemPosition).getType();
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return oldData.get(oldItemPosition).equals(newData.get(newItemPosition));
}
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
return super.getChangePayload(oldItemPosition, newItemPosition);
}
}
4.2、切換數據集
既然已經有了 DiffUtil.Callback 的實現之后,我們就需要對切換數據集的點擊事件進行處理了。
public void changeData(View view) {
final ArrayList<Bean> oldData = dataArray[index];
index++;
index = index % dataArray.length;
final ArrayList<Bean> newData = dataArray[index];
mDatas1 = newData;
DiffUtil.DiffResult result = DiffUtil.calculateDiff(new AdapterDiffCallBack(oldData,newData), false);
result.dispatchUpdatesTo(recyclerView.getAdapter());
}
注意:Google 官方同時也指出,如果是對大數據集的比對,最好是方在子線程中去完成計算,也就是其實是存在堵塞 UI 的情況的。所以如果你遇見了使用 DiffUtil 之后,每次刷新有卡頓的情況,可以考慮是否數據集太大,是否應該在子線程中完成計算。