1. 程式人生 > >關於RecyclerView#DiffUtil一點總結(一)

關於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的基本原理。