關於RecyclerView#DiffUtil一點總結(一)
前言
最近在專案中遇到一個需求,在載入首頁列表資訊時,先是要載入快取內容[寫在檔案中],如果網路請求有資料,則替換快取內容,並顯示新的內容。需求本來很簡單,直接一個RecyclerView解決了,寫完程式碼就提交了,比如這樣:
請原諒資料上的美女圖,公司的專案就是如此。其實快取中的資料與新資料,只有第三條發生了改變,其他的都一毛一樣;而且你在重新整理的時候,會發現一道白光閃過,有人抱怨體驗不太好,那好,我改改,改完之後,變成這個樣子了:
左邊是不太講究的直接RecyclewView.Adapter.notifyItemRangeChanged()的效果,右邊的是使用了android.support.v7.util.DiffUtil這個幫助類實現的效果,可以說,差距還是比較大,那麼今天就好好說一下這個DiffUtil的作用。
說明
這個DiffUtil存在的意義,是專門為RecyclerView更新item設計的。回想一下,我們當初從ListView中,使用BaseAdapter.notifyDataChanged()無腦全屏重新整理,到RecyclerView.Adapter提供了notifyItem**系列,似的RecyclerView重新整理時,可以精確到具體的目標item;最後到使用DiffUtil.DiffResult可以精確到某個Item上某個具體的TextView,或者ImageView的重新整理。一步一步走來,你會發現,我們可以控制重新整理的可視範圍越來越精確,進而會使我們的app每次重新整理消耗的資源越來越小,效果越來越nice。扯了這麼多,還是還來看看傳說中的DiffUtil的使用方法吧。
使用
首先我們定義User物件,作為每個RecyclerView 條目的資料載體:
public class User {
// 使用者Id
public int id ;
// 使用者名稱稱
public String name;
//使用者影象
public String url ;
//使用者年齡
public int age;
}
定義一個Callback繼承DiffUtil.Callback,這個比較重要:
public class UserItemDiffCallBack extends DiffUtil.Callback {
//舊的資料集合
private List<User> mOldUserList;
//新的資料集合
private List<User> mNewUserList;
//構造方法 傳入舊的資料結構和新的資料結構
public UserItemDiffCallBack(List<User> oldUserList, List<User> newUserList) {
this.mOldUserList = oldUserList;
this.mNewUserList = newUserList;
}
//獲取舊的資料量大小
@Override
public int getOldListSize() {
return null == mOldUserList ? 0 : mOldUserList.size();
}
//獲取新的資料量大小
@Override
public int getNewListSize() {
return null == mNewUserList ? 0 : mNewUserList.size();
}
//判斷兩個條目是否是一致的
//在真實的專案中,我們一般使用id或者index搜尋來判斷兩條item是否一致
//如果我們的id一樣,在系統裡面我就認為兩個資料記錄是一樣的
@Override
public boolean areItemsTheSame(int oldPosition, int newPosition) {
return mOldUserList.get(oldPosition).id == mNewUserList.get(newPosition).id;
}
//這個需要areItemsTheSame 返回true時才呼叫
//即使我們的id是一致的,我們在系統中是同一個物件,但是的name可能更新,或者影象可能更新了
//這裡可以填寫自己的邏輯,如果影象是一致的,我就認為內容沒有變化
@Override
public boolean areContentsTheSame(int oldPosition, int newPosition) {
return TextUtils.equals(mOldUserList.get(oldPosition).url, mNewUserList.get(newPosition).url);
}
//這個呼叫比較奇葩,要求也蠻多的,它需要areItemsTheSame()返回true,說明是同一條資料
//但是又需要areContentsTheSame()返回false,告訴你雖然我們是同一條資料,但是我們也有不同的
//它返回的是Object物件,我這裡是返回的是Boolean物件,等會告訴你怎麼用這個物件
//當然了,你也可以返回任意的物件,到時候裝換一下就可以了。
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
User newUserEntity = mNewUserList.get(newItemPosition);
return oldUserEntity.url.equals(newUserEntity.url);
}
}
UserItemDiffCallBack完成之後,需要在RecyclerView.Adapter中呼叫含有三個引數的onBindViewHolder方法:
// 這裡的 payloads就是UserItemDiffCallBack中getChangePayload中返回的Object集合
// 如果某個條目沒有呼叫UserItemDiffCallBack#getChangePayload方法,那麼那個條目對應的
// onBindViewHolder中payloads就會為空陣列物件
//由於我返回的判斷新舊資料的url是否相同,所以直接更新一個item的照片就可以了,對於Item其他的TextView對應的
//name和age,資料沒有變化,就沒有必要更新了。
//這裡就是上面所說的,可以精確到某個View的更新了,比notifyItemChanged更加有效了。
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) {
if (payloads.isEmpty()) {
super.onBindViewHolder(holder, position, payloads);
return;
}
ImageLoader.load(holder.iv, mList.get(position).url);
}
最後,有新舊資料更新時,在RecyclerView.Adapter中新增方法
public void setData(List<User> userList) {
// 獲取DiffResut結果
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new UserItemDiffCallBack(mList, userList));
//使用DiffResult分發給apdate熱更新
diffResult.dispatchUpdatesTo(this);
//按照DiffeResult定義好的邏輯,更新資料
mList.clear();
mList.addAll(userList);
}
使用時:
List<User> tempList = getNewUserList();
mCommonAdapter2.setData(tempList);
此時更新資料時,將不再會有滿屏白光閃爍,只更新資料變換的項,這個比notifyItemChanged(int position)帥氣多了。
對於DiffUtil,基本流程圖可以看一下下圖:
本篇算是一個小小的開端,下一篇我打算總結一下DiffUtil的基本原理。