1咬展、前言
DIffUtils 是 Support-v7:24:2.0 中耸峭,更新的工具類澜躺。
它主要是為了配合 RecyclerView 使用蝉稳,通過比對(duì)新、舊兩個(gè)數(shù)據(jù)集的差異掘鄙,生成舊數(shù)據(jù)到新數(shù)據(jù)的最小變動(dòng)耘戚,然后對(duì)有變動(dòng)的數(shù)據(jù)項(xiàng),進(jìn)行局部刷新操漠。
當(dāng)然收津,DiffUtil 不僅只能配合 RecyclerView 使用,它實(shí)際上可以單獨(dú)用于比對(duì)兩個(gè)數(shù)據(jù)集,然后如何操作是可以定制的撞秋,那么在什么場景下使用长捧,就全憑我們自己發(fā)揮了。
2吻贿、DiffUtil
DiffUtil 在使用起來串结,主要需要關(guān)注幾個(gè)類:
- DiffUtil.Callback:具體用于限定數(shù)據(jù)集比對(duì)規(guī)則。
- DiffUtil.DiffResult:比對(duì)數(shù)據(jù)集之后舅列,返回的差異結(jié)果肌割。
2.1、DiffUtil.Callback
DiffUtil.Callback 主要就是為了限定兩個(gè)數(shù)據(jù)集中剧蹂,子項(xiàng)的比對(duì)規(guī)則声功。畢竟開發(fā)者面對(duì)的數(shù)據(jù)結(jié)構(gòu)多種多樣,既然沒法做一套通用的內(nèi)容比對(duì)方式宠叼,那么就將比對(duì)的規(guī)則先巴,交還給開發(fā)者來實(shí)現(xiàn)即可。
在 Callback 中冒冬,其實(shí)只需要實(shí)現(xiàn) 4 個(gè)方法:
- getOldListSize():舊數(shù)據(jù)集的長度伸蚯。
- getNewListSize():新數(shù)據(jù)集的長度
- areItemsTheSame():判斷是否是同一個(gè)Item。
- areContentsTheSame():如果是通一個(gè)Item简烤,此方法用于判斷是否同一個(gè) Item 的內(nèi)容也相同剂邮。
前兩個(gè)是獲取數(shù)據(jù)集長度的方法,這沒什么好說的横侦。但是后兩個(gè)方法挥萌,主要是為了對(duì)應(yīng)多布局的情況產(chǎn)生的,也就是存在多個(gè) viewType的情況枉侧。首先需要使用 areItemsTheSame()
方法比對(duì)是否來自同一個(gè) viewType
引瀑,然后再通過 areContentsTheSame()
方法比對(duì)其內(nèi)容是否也相等。
其實(shí) Callback 還有一個(gè) getChangePayload()
的方法榨馁,它可以在 ViewType 相同憨栽,但是內(nèi)容不相同的時(shí)候
,用 payLoad 記錄需要在這個(gè) ViewHolder 中翼虫,然后去更新具體的View屑柔。
areItemsTheSame()、areContentsTheSame()珍剑、getChangePayload()
分別代表了不同量級(jí)的刷新掸宛。
首先會(huì)通過
areItemsTheSame()
判斷當(dāng)前 position 下的ViewType 是否一致,如果不一致就表明當(dāng)前 position 下招拙,從數(shù)據(jù)到 UI 結(jié)構(gòu)上全部變化了旁涤,那么就不關(guān)心內(nèi)容(areContentsTheSame()翔曲、getChangePayload()不會(huì)被調(diào)用
),直接更新就好了劈愚。如果一致的話瞳遍,那么其實(shí) View 是可以復(fù)用的,就還需要再通過areContentsTheSame()
方法判斷其內(nèi)容是否一致菌羽,如果一致掠械,則表示是同一條數(shù)據(jù),不需要做額外的操作注祖。但是一旦不一致猾蒂,則還會(huì)調(diào)用getChangePayload()
來標(biāo)記到底是哪個(gè)地方的不一樣,最終標(biāo)記需要更新的地方是晨,最終返回給 DiffResult 肚菠。
當(dāng)然,對(duì)性能要是要求沒那么高的情況下罩缴,是可以不使用 getChangedPayload()
方法的蚊逢。
2.2、DiffUtil.DiffResult
DiffUtil.DiffResult 其實(shí)就是 DiffUtil 通過 DiffUtil.Callback 計(jì)算出來箫章,兩個(gè)數(shù)據(jù)集的差異烙荷。它是可以直接使用在 RecyclerView 上的。如果有必要檬寂,也是可以通過實(shí)現(xiàn) ListUpdateCallback 接口终抽,來比對(duì)這些差異的。
3桶至、使用DiffUtil
介紹了 Callback 和 DiffResult 之后昼伴,其實(shí)就可以正常使用 DiffUtil 來進(jìn)行數(shù)據(jù)集的比對(duì)了。
在這個(gè)過程中镣屹,其實(shí)真的很簡單圃郊,只需要調(diào)用兩個(gè)方法:
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallBack(oldDatas, newDatas), true);
diffResult.dispatchUpdatesTo(mAdapter);
calculateDiff 方法主要是用于通過一個(gè)具體的 DiffUtils.Callback 實(shí)現(xiàn)對(duì)象,來計(jì)算出兩個(gè)數(shù)據(jù)集差異的結(jié)果野瘦,得到 DiffUtil.DiffResult 描沟。
而 calculateDiff 的另外一個(gè)參數(shù)飒泻,用于標(biāo)記是否需要檢測 Item 的移動(dòng)鞭光。
而 dispatchUpdatesTo()
就是將這個(gè)數(shù)據(jù)集差異的結(jié)果,通過 Adapter 更新到 RecyclerView 上面泞遗。
實(shí)際上 dispatchUpdatesTo(Adapter) 惰许,也是使用的 ListUpdateCallback 這個(gè)接口,在其中獲得差異史辙,然后調(diào)用 Adapter 的對(duì)應(yīng)方法汹买。
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佩伤、實(shí)例
既然已經(jīng)說清楚了,那么我們開始上例子了晦毙。
功能很簡單生巡,有四個(gè)數(shù)據(jù)集,使用 RecyclerView 承載见妒,然后有一個(gè)按鈕孤荣,用于輪換的切換數(shù)據(jù)集。
4.1须揣、實(shí)現(xiàn) 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盐股、切換數(shù)據(jù)集
既然已經(jīng)有了 DiffUtil.Callback 的實(shí)現(xiàn)之后,我們就需要對(duì)切換數(shù)據(jù)集的點(diǎn)擊事件進(jìn)行處理了耻卡。
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 官方同時(shí)也指出疯汁,如果是對(duì)大數(shù)據(jù)集的比對(duì),最好是方在子線程中去完成計(jì)算卵酪,也就是其實(shí)是存在堵塞 UI 的情況的幌蚊。所以如果你遇見了使用 DiffUtil 之后,每次刷新有卡頓的情況凛澎,可以考慮是否數(shù)據(jù)集太大霹肝,是否應(yīng)該在子線程中完成計(jì)算。