Android-DiffUtil以及最新ListAdapter介紹

本文主要介紹Android系統中提供的工具類DiffUtil,DiffUtil的主是用與RecyclerView的局部更新,從而提高頁面刷新效率。

本文基于最新的v7-27.1.1版本的RecyclerView做介紹,因為這個兼容包里面新增了一個ListAdapter,一并做介紹。

DiffUtil

DiffUtil是在安卓7.0上引入的工具類,相對于“傳統”的nofityDataChange()方式:

  1. DiffUtil可以讓我們做到數據局部刷新。雖然我們也可以手動去記錄數據改變的位置,自己通過notifyItemRangeInserted等方法去更新數據,但是麻煩~
  2. DiffUtil可以在局部數據刷新的時候顯示動畫。

先上demo,看一下DiffUtil要怎么用

簡易用法

public static final class MyDiffUtilCallback extends DiffUtil.Callback {

        private List<Bean> mOldList;
        private List<Bean> mNewList;

        public MyDiffUtilCallback setDates(List<Bean> oldList, List<Bean> newList) {
            mOldList = oldList;
            mNewList = newList;

            return this;
        }

        @Override
        public int getOldListSize() {
            return mOldList.size();
        }

        @Override
        public int getNewListSize() {
            return mNewList.size();
        }

        @Override
        public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
            return mOldList.get(oldItemPosition).id == mNewList.get(newItemPosition).id;
//            return true;
        }

        @Override
        public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
            return mOldList.get(oldItemPosition).content.equals(mNewList.get(newItemPosition).content);
        }
    }
    
    
    ...
    
    
public void onNewData(List<Bean> newData) {                                                                    
                                                                                                               
    //數據源太大時計算費時,放入子線程                                                                                         
    //最新版本兼容包已出ListAdapter,自帶子線程執行                                                                                                                                                            
        DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(mMyDiffUtilCallback.setDates(mBeans, newData));
        diffResult.dispatchUpdatesTo(MyAdapter.this);                                                          
        mBeans.clear();                                                                                        
        mBeans.addAll(newData);                                                                                                                                                                                      
}                                                                                                              

上面的代碼很簡單:

  1. 就是實現DiffUtil.Callback,指定數據差異的規則
  2. 在有新數據時,通過DiffUtil.calculateDiff(Callback cb)計算新老數據的差異
  3. 將計算完的結果更新到adapter中

我們可以看到DiffUtil.Callback主要有4個抽象方法需要實現,看一下這4個方法的具體作用。

public abstract int getOldListSize();                               
public abstract int getNewListSize();

上面2個方法主要獲取新老數據的長度。

//返回值表示新數據傳入時這兩個位置的數據是否時同一個條目                                                                                       
public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition);
//返回值表示新老位置的數據內容是否相同,這個方法在areItemsTheSame()返回true時生效                                                                                                                                                                                  
public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition);      

上面這兩個方法就是區分兩個數據bean是否相同。

敲黑板,這里有個注意點:

當areItemsTheSame返回為false時,不管areContentsTheSame是否為true,adapter中的條目都會更新

高級用法

DiffUtil.Callback中還有另外一個可以復寫的方法

@Nullable                                                                 
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
    return null;                                                          
}                                                                         

當areItemsTheSame()返回true,同時areContentsTheSame()返回false時,通過這個方法可以用來返回具體的數據差異。

我們來看一下用法

@Override                                                                                      
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {                     
    return mOldList.get(oldItemPosition).id == mNewList.get(newItemPosition).id;               
      return true;                                                                             
}                                                                                              
                                                                                               
@Override                                                                                      
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {                  
    return false;
}                                                                                              
                                                                                               
@Nullable                                                                                      
@Override                                                                                      
public Object getChangePayload(int oldItemPosition, int newItemPosition) {                     
    Bean oldBean = mOldList.get(oldItemPosition);                                              
    Bean newBean = mNewList.get(newItemPosition);                                              
                                                                                               
    Bundle bundle = new Bundle();                                                              
                                                                                               
    if (!oldBean.content.equals(newBean.content)) {                                            
        bundle.putString("content", newBean.content);                                          
    }                                                                                          
                                                                                               
    if (bundle.size() > 0) {                                                                   
        return bundle;                                                                         
    }                                                                                          
                                                                                               
    return null;                                                                               
}                                                                                              

然后在Recycler的Adapter中我們通過onBindViewHolder()的另一個重載方法獲取到之前設置的數據進行局部刷新。

@Override                                                                                        
public void onBindViewHolder(MyAdapter.ViewHolder holder, int position, @NonNull List payloads) {
                                                                                                 
    if (payloads.isEmpty()) {                                                                    
        super.onBindViewHolder(holder, position, payloads); 
        //內部其實就是調用的全部刷新的方法                                     
        //onBindViewHolder(holder, position);                                                    
    } else {                                                                                     
        Bundle bundle = (Bundle) payloads.get(0);                                                
        for (String key : bundle.keySet()) {                                                     
            if (key.equals("content")) {                                                         
                holder.contentView.setText(bundle.getString("content"));                         
            }                                                                                    
        }                                                                                        
    }                                                                                            
}                                                                                                

好了,這里就是DiffUtil的用法了,實現其中的Callback用戶比較數據的差異,當來新數據時,調用其中的calculateDiff()靜態方法計算數據差異即可。如果你想實現RecyclerView的局部刷新,那可以實現getChangePayload。

ListAdapter

當數據量不大時,我們可以在UI線程中直接更新數據,但是當數據量大時這就比較尷尬了,我們需要自己放在子線程操作,然后再回UI線程更新頁面。

在7.0上引入DiffUtil之后,現在又在最新的v7的27.1.1兼容包中加入的官方的支持,就是ListAdapter。

public abstract class ListAdapter<T, VH extends RecyclerView.ViewHolder>
        extends RecyclerView.Adapter<VH> {
    private final AsyncListDiffer<T> mHelper;

    @SuppressWarnings("unused")
    protected ListAdapter(@NonNull DiffUtil.ItemCallback<T> diffCallback) {
        mHelper = new AsyncListDiffer<>(new AdapterListUpdateCallback(this),
                new AsyncDifferConfig.Builder<>(diffCallback).build());
    }

    @SuppressWarnings("unused")
    protected ListAdapter(@NonNull AsyncDifferConfig<T> config) {
        mHelper = new AsyncListDiffer<>(new AdapterListUpdateCallback(this), config);
    }

    /**
     * Submits a new list to be diffed, and displayed.
     * <p>
     * If a list is already being displayed, a diff will be computed on a background thread, which
     * will dispatch Adapter.notifyItem events on the main thread.
     *
     * @param list The new list to be displayed.
     */
    @SuppressWarnings("WeakerAccess")
    public void submitList(List<T> list) {
        mHelper.submitList(list);
    }

    @SuppressWarnings("unused")
    protected T getItem(int position) {
        return mHelper.getCurrentList().get(position);
    }

    @Override
    public int getItemCount() {
        return mHelper.getCurrentList().size();
    }
}

ListAdapter用法就跟普通的RecyclerView并無太大差別。

  1. 一共2個構造方法,區別在于第二個構造方法可以自己指定執行的線程。
  2. submitList(),用于提交新數據,更新UI。
  3. 數據比較操作的線程操作在AsyncListDiffer中實現,代碼也比較簡單,這里就不再貼代碼分析了。需要注意點一點時,通過AsyncListDiffer返回的List是一個UnmodifiableList,意味著不能改變長度。

總結時間

  1. DiffUtil提供了梅耶斯算法實現的新老數據比較的方法,數據比較的規則在Callback
    中讓用戶自行實現,同時實現getChangePayload配合Adaper中的onBindViewHolder(MyAdapter.ViewHolder holder, int position, @NonNull List payloads)可以實現數據的局部更新。
  2. ListAdapter是對RecyclerView傳統Adapter的一個拓展,在子線程中利用DiffUtil比較數據,并在UI線程更新。
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,476評論 25 708
  • 【想法是身體的活動,還是靈魂的活動?】 如果想只是靈魂的活動, 為什么想多了頭會痛, 想久了人會餓, 不想了人會遲...
    巴木驢閱讀 286評論 0 0
  • 歌聲,是多么的神奇, 它不需要敘述故事給我們聽, 只要聽著曲子的旋律, 就知道作者或歌所要表達的情感。 為豐富同學...
    西南林業大學閱讀 287評論 0 0
  • 作者: 趙志敏 上到山頂,志偉電話聯系接到了我們。山頂好闊。很大一個停車場。有一牌坊。左邊是“軒轅廟”國務院200...
    快樂的人ZZM閱讀 1,465評論 0 3
  • 父親是一座靜寞的山, 咽下了生活的苦痛, 扛起了家庭的重擔, 他人未老 背已佝僂, 他年未老, 已霜滿頭, 生活給...
    大漠走雨閱讀 195評論 0 0