天天看點

【Android】RecyclerView的好伴侶:詳解DiffUtil一 概述二 DiffUtil的簡單用法三 DiffUtil的進階用法四 在子線程中使用DiffUtil五總結和其他

本篇文章已授權微信公衆号 hongyangAndroid (鴻洋)獨家釋出

轉載請标明出處: http://blog.csdn.net/zxt0601/article/details/52562770

本文出自:【張旭童的部落格】 (http://blog.csdn.net/zxt0601)

一 概述

DiffUtil是support-v7:24.2.0中的新工具類,它用來比較兩個資料集,尋找出舊資料集-》新資料集的最小變化量。

說到資料集,相信大家知道它是和誰相關的了,就是我的最愛,RecyclerView。

就我使用的這幾天來看,它最大的用處就是在RecyclerView重新整理時,不再無腦

mAdapter.notifyDataSetChanged()

以前無腦

mAdapter.notifyDataSetChanged()

有兩個缺點:

  1. 不會觸發RecyclerView的動畫(删除、新增、位移、change動畫)
  2. 性能較低,畢竟是無腦的重新整理了一遍整個RecyclerView , 極端情況下:新老資料集一模一樣,效率是最低的。

使用DiffUtil後,改為如下代碼:

DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallBack(mDatas, newDatas), true);
diffResult.dispatchUpdatesTo(mAdapter);
           

它會自動計算新老資料集的差異,并根據差異情況,自動調用以下四個方法

adapter.notifyItemRangeInserted(position, count);
adapter.notifyItemRangeRemoved(position, count);
adapter.notifyItemMoved(fromPosition, toPosition);
adapter.notifyItemRangeChanged(position, count, payload);
           

顯然,這個四個方法在執行時都是伴有RecyclerView的動畫的,且都是定向重新整理方法,重新整理效率蹭蹭的上升了。

老規矩,先上圖,

圖一是無腦

mAdapter.notifyDataSetChanged()

的效果圖,可以看到重新整理互動很生硬,Item突然的出現在某個位置:

【Android】RecyclerView的好伴侶:詳解DiffUtil一 概述二 DiffUtil的簡單用法三 DiffUtil的進階用法四 在子線程中使用DiffUtil五總結和其他

圖二是使用DiffUtils的效果圖,最明顯的是有插入、移動Item的動畫:

【Android】RecyclerView的好伴侶:詳解DiffUtil一 概述二 DiffUtil的簡單用法三 DiffUtil的進階用法四 在子線程中使用DiffUtil五總結和其他

轉成GIF有些渣,下載下傳文末Demo運作效果更佳哦。

本文将包含且不僅包含以下内容:

1 先介紹DiffUtil的簡單用法,實作重新整理時的“增量更新”效果。(“增量更新”是我自己的叫法)

2 DiffUtil的進階用法,在某項Item隻有内容(data)變化,位置(position)未變化時,完成部分更新(官方稱之為Partial bind,部分綁定)。

3 了解到 RecyclerView.Adapter還有

public void onBindViewHolder(VH holder, int position, List<Object> payloads)

方法,并掌握它。

4 在子線程中計算DiffResult,在主線程中重新整理RecyclerView。

5 少部分人不喜歡的notifyItemChanged()導緻Item白光一閃的動畫 如何去除。

6 DiffUtil部分類、方法 官方注釋的漢化

二 DiffUtil的簡單用法

前文也提到,DiffUtil是幫助我們在重新整理RecyclerView時,計算新老資料集的差異,并自動調用RecyclerView.Adapter的重新整理方法,以完成高效重新整理并伴有Item動畫的效果。

那麼我們在學習它之前要先做一些準備工作,先寫一個普通青年版,無腦notifyDataSetChanged()重新整理的Demo。

1 一個普通的JavaBean,但是實作了clone方法,僅用于寫Demo模拟重新整理用,實際項目不需要,因為重新整理時,資料都是從網絡拉取的。:

class TestBean implements Cloneable {
    private String name;
    private String desc;
    ....//get set方法省略
    //僅寫DEMO 用 實作克隆方法
    @Override
    public TestBean clone() throws CloneNotSupportedException {
        TestBean bean = null;
        try {
            bean = (TestBean) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return bean;
    }
           

2 實作一個普普通通的RecyclerView.Adapter。

public class DiffAdapter extends RecyclerView.Adapter<DiffAdapter.DiffVH> {
    private final static String TAG = "zxt";
    private List<TestBean> mDatas;
    private Context mContext;
    private LayoutInflater mInflater;

    public DiffAdapter(Context mContext, List<TestBean> mDatas) {
        this.mContext = mContext;
        this.mDatas = mDatas;
        mInflater = LayoutInflater.from(mContext);
    }

    public void setDatas(List<TestBean> mDatas) {
        this.mDatas = mDatas;
    }

    @Override
    public DiffVH onCreateViewHolder(ViewGroup parent, int viewType) {
        return new DiffVH(mInflater.inflate(R.layout.item_diff, parent, false));
    }

    @Override
    public void onBindViewHolder(final DiffVH holder, final int position) {
        TestBean bean = mDatas.get(position);
        holder.tv1.setText(bean.getName());
        holder.tv2.setText(bean.getDesc());
        holder.iv.setImageResource(bean.getPic());
    }

    @Override
    public int getItemCount() {
        return mDatas != null ? mDatas.size() : ;
    }

    class DiffVH extends RecyclerView.ViewHolder {
        TextView tv1, tv2;
        ImageView iv;

        public DiffVH(View itemView) {
            super(itemView);
            tv1 = (TextView) itemView.findViewById(R.id.tv1);
            tv2 = (TextView) itemView.findViewById(R.id.tv2);
            iv = (ImageView) itemView.findViewById(R.id.iv);
        }
    }
}
           

3 Activity代碼:

public class MainActivity extends AppCompatActivity {
    private List<TestBean> mDatas;
    private RecyclerView mRv;
    private DiffAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initData();
        mRv = (RecyclerView) findViewById(R.id.rv);
        mRv.setLayoutManager(new LinearLayoutManager(this));
        mAdapter = new DiffAdapter(this, mDatas);
        mRv.setAdapter(mAdapter);
    }

    private void initData() {
        mDatas = new ArrayList<>();
        mDatas.add(new TestBean("張旭童1", "Android", R.drawable.pic1));
        mDatas.add(new TestBean("張旭童2", "Java", R.drawable.pic2));
        mDatas.add(new TestBean("張旭童3", "背鍋", R.drawable.pic3));
        mDatas.add(new TestBean("張旭童4", "手撕産品", R.drawable.pic4));
        mDatas.add(new TestBean("張旭童5", "手撕測試", R.drawable.pic5));
    }

    /**
     * 模拟重新整理操作
     *
     * @param view
     */
    public void onRefresh(View view) {
        try {
            List<TestBean> newDatas = new ArrayList<>();
            for (TestBean bean : mDatas) {
                newDatas.add(bean.clone());//clone一遍舊資料 ,模拟重新整理操作
            }
            newDatas.add(new TestBean("趙子龍", "帥", R.drawable.pic6));//模拟新增資料
            newDatas.get().setDesc("Android+");
            newDatas.get().setPic(R.drawable.pic7);//模拟修改資料
            TestBean testBean = newDatas.get();//模拟資料位移
            newDatas.remove(testBean);
            newDatas.add(testBean);
            //别忘了将新資料給Adapter
            mDatas = newDatas;
            mAdapter.setDatas(mDatas);
            mAdapter.notifyDataSetChanged();//以前我們大多數情況下隻能這樣
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }

}
           

很簡單,隻不過在建構新資料源newDatas時,是周遊老資料源mDatas,調用每個data的clone()方法,確定新老資料源雖然資料一緻,但是記憶體位址(指針不一緻),這樣在後面修改newDatas裡的值時,不會牽連mDatas裡的值被一起改了。

4 activity_main.xml 删掉了一些寬高代碼,就是一個RecyclerView和一個Button用于模拟重新整理。:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv" />

    <Button
        android:id="@+id/btnRefresh"
        android:layout_alignParentRight="true"
        android:onClick="onRefresh"
        android:text="模拟重新整理" />
</RelativeLayout>
           

以上是一個普通青年很容易寫出的,無腦notifyDataSetChanged()的demo,運作效果如第一節圖一。

但是我們都要争做文藝青年,so

下面開始進入正題,簡單使用DiffUtil,我們需要且僅需要額外編寫一個類。

想成為文藝青年,我們需要實作一個繼承自

DiffUtil.Callback

的類,實作它的四個abstract方法。

雖然這個類叫Callback,但是把它了解成:定義了一些用來比較新老Item是否相等的契約(Contract)、規則(Rule)的類, 更合适。

DiffUtil.Callback

抽象類如下:

public abstract static class Callback {
        public abstract int getOldListSize();//老資料集size

        public abstract int getNewListSize();//新資料集size

        public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition);//新老資料集在同一個postion的Item是否是一個對象?(可能内容不同,如果這裡傳回true,會調用下面的方法)

        public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition);//這個方法僅僅是上面方法傳回ture才會調用,我的了解是隻有notifyItemRangeChanged()才會調用,判斷item的内容是否有變化

        //該方法在DiffUtil進階用法中用到 ,暫且不提
        @Nullable
        public Object getChangePayload(int oldItemPosition, int newItemPosition) {
            return null;
        }
    }
           

本Demo如下實作

DiffUtil.Callback

,核心方法配有中英雙語注釋(說人話就是,翻譯了官方的英文注釋,友善大家更好了解)。

/**
 * 介紹:核心類 用來判斷 新舊Item是否相等
 * 作者:zhangxutong
 * 郵箱:[email protected]
 * 時間: 2016/9/12.
 */

public class DiffCallBack extends DiffUtil.Callback {
    private List<TestBean> mOldDatas, mNewDatas;//看名字

    public DiffCallBack(List<TestBean> mOldDatas, List<TestBean> mNewDatas) {
        this.mOldDatas = mOldDatas;
        this.mNewDatas = mNewDatas;
    }

    //老資料集size
    @Override
    public int getOldListSize() {
        return mOldDatas != null ? mOldDatas.size() : ;
    }

    //新資料集size
    @Override
    public int getNewListSize() {
        return mNewDatas != null ? mNewDatas.size() : ;
    }

    /**
     * Called by the DiffUtil to decide whether two object represent the same Item.
     * 被DiffUtil調用,用來判斷 兩個對象是否是相同的Item。
     * For example, if your items have unique ids, this method should check their id equality.
     * 例如,如果你的Item有唯一的id字段,這個方法就 判斷id是否相等。
     * 本例判斷name字段是否一緻
     *
     * @param oldItemPosition The position of the item in the old list
     * @param newItemPosition The position of the item in the new list
     * @return True if the two items represent the same object or false if they are different.
     */
    @Override
    public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
        return mOldDatas.get(oldItemPosition).getName().equals(mNewDatas.get(newItemPosition).getName());
    }

    /**
     * Called by the DiffUtil when it wants to check whether two items have the same data.
     * 被DiffUtil調用,用來檢查 兩個item是否含有相同的資料
     * DiffUtil uses this information to detect if the contents of an item has changed.
     * DiffUtil用傳回的資訊(true false)來檢測目前item的内容是否發生了變化
     * DiffUtil uses this method to check equality instead of {@link Object#equals(Object)}
     * DiffUtil 用這個方法替代equals方法去檢查是否相等。
     * so that you can change its behavior depending on your UI.
     * 是以你可以根據你的UI去改變它的傳回值
     * For example, if you are using DiffUtil with a
     * {@link android.support.v7.widget.RecyclerView.Adapter RecyclerView.Adapter}, you should
     * return whether the items' visual representations are the same.
     * 例如,如果你用RecyclerView.Adapter 配合DiffUtil使用,你需要傳回Item的視覺表現是否相同。
     * This method is called only if {@link #areItemsTheSame(int, int)} returns
     * {@code true} for these items.
     * 這個方法僅僅在areItemsTheSame()傳回true時,才調用。
     * @param oldItemPosition The position of the item in the old list
     * @param newItemPosition The position of the item in the new list which replaces the
     *                        oldItem
     * @return True if the contents of the items are the same or false if they are different.
     */
    @Override
    public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
        TestBean beanOld = mOldDatas.get(oldItemPosition);
        TestBean beanNew = mNewDatas.get(newItemPosition);
        if (!beanOld.getDesc().equals(beanNew.getDesc())) {
            return false;//如果有内容不同,就傳回false
        }
        if (beanOld.getPic() != beanNew.getPic()) {
            return false;//如果有内容不同,就傳回false
        }
        return true; //預設兩個data内容是相同的
    }
           

注釋張寫了這麼詳細的注釋+簡單的代碼,相信一眼可懂。

然後在使用時,注釋掉你以前寫的notifyDatasetChanged()方法吧,替換成以下代碼:

//文藝青年新寵
//利用DiffUtil.calculateDiff()方法,傳入一個規則DiffUtil.Callback對象,和是否檢測移動item的 boolean變量,得到DiffUtil.DiffResult 的對象

DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallBack(mDatas, newDatas), true);

//利用DiffUtil.DiffResult對象的dispatchUpdatesTo()方法,傳入RecyclerView的Adapter,輕松成為文藝青年

diffResult.dispatchUpdatesTo(mAdapter);

//别忘了将新資料給Adapter
mDatas = newDatas;
mAdapter.setDatas(mDatas);
           

講解:

步驟一

在将newDatas 設定給Adapter之前,先調用DiffUtil.calculateDiff()方法,計算出新老資料集轉化的最小更新集,就是DiffUtil.DiffResult對象。

DiffUtil.calculateDiff()方法定義如下:

第一個參數是DiffUtil.Callback對象,

第二個參數代表是否檢測Item的移動,改為false算法效率更高,按需設定,我們這裡是true。

public static DiffResult calculateDiff(Callback cb, boolean detectMoves)
           

步驟二

然後利用DiffUtil.DiffResult對象的

dispatchUpdatesTo()

方法,傳入RecyclerView的Adapter,替代普通青年才用的

mAdapter.notifyDataSetChanged()

方法。

檢視源碼可知,該方法内部,就是根據情況調用了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);
                }
            });
        }
           

小結:

是以說,DiffUtil不僅僅隻能和RecyclerView配合,我們也可以自己實作

ListUpdateCallback

接口的四個方法去做一些事情。(我暫時不負責任随便一項想,想到可以配合自己項目裡的九宮格控件?或者優化我上篇文章寫的NestFullListView?小安利,見 ListView、RecyclerView、ScrollView裡嵌套ListView 相對優雅的解決方案:http://blog.csdn.net/zxt0601/article/details/52494665)

至此,我們已進化成文藝青年,運作效果和第一節圖二基本一緻,

唯一不同的是此時

adapter.notifyItemRangeChanged()

會有Item白光一閃的更新動畫 (本文Demo的postion為0的item)。 這個Item一閃的動畫有人喜歡有人恨,不過都不重要了,

因為當我們學會了第三節的DiffUtil搞基用法,你愛不愛這個ItemChange動畫,它都将随風而去。(不知道是不是官方bug)

效果就是第一節的圖二,我們的item0其實圖檔和文字都變化了,但是這個改變并沒有伴随任何動畫。

讓我們邁向 文藝青年中的文藝青年 之路。

三 DiffUtil的進階用法

理論:

進階用法隻涉及到兩個方法,

我們需要分别實作

DiffUtil.Callback

public Object getChangePayload(int oldItemPosition, int newItemPosition)

方法,

傳回的Object就是表示Item改變了哪些内容。

再配合

RecyclerView.Adapter

public void onBindViewHolder(VH holder, int position, List<Object> payloads)

方法,

完成定向重新整理。(成為文青中的文青,文青青。)

敲黑闆,這是一個新方法,注意它有三個參數,前兩個我們熟,第三個參數就包含了我們在

getChangePayload()

傳回的Object。

好吧,那我們就先看看這個方法是何方神聖:

在v7-24.2.0的源碼裡,它長這個樣子:

/**
         * Called by RecyclerView to display the data at the specified position. This method
         * should update the contents of the {@link ViewHolder#itemView} to reflect the item at
         * the given position.
         * <p>
         * Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method
         * again if the position of the item changes in the data set unless the item itself is
         * invalidated or the new position cannot be determined. For this reason, you should only
         * use the <code>position</code> parameter while acquiring the related data item inside
         * this method and should not keep a copy of it. If you need the position of an item later
         * on (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will
         * have the updated adapter position.
         * <p>
         * Partial bind vs full bind:
         * <p>
         * The payloads parameter is a merge list from {@link #notifyItemChanged(int, Object)} or
         * {@link #notifyItemRangeChanged(int, int, Object)}.  If the payloads list is not empty,
         * the ViewHolder is currently bound to old data and Adapter may run an efficient partial
         * update using the payload info.  If the payload is empty,  Adapter must run a full bind.
         * Adapter should not assume that the payload passed in notify methods will be received by
         * onBindViewHolder().  For example when the view is not attached to the screen, the
         * payload in notifyItemChange() will be simply dropped.
         *
         * @param holder The ViewHolder which should be updated to represent the contents of the
         *               item at the given position in the data set.
         * @param position The position of the item within the adapter's data set.
         * @param payloads A non-null list of merged payloads. Can be empty list if requires full
         *                 update.
         */
        public void onBindViewHolder(VH holder, int position, List<Object> payloads) {
            onBindViewHolder(holder, position);
        }
           

原來它内部就僅僅調用了兩個參數的

onBindViewHolder(holder, position)

,(題外話,哎喲喂,我的NestFullListView 的Adapter也有幾分神似這種寫法,看來我離Google大神又近了一步)

看到這我才明白,其實onBind的入口,就是這個方法,它才是和

onCreateViewHolder

對應的方法,

源碼往下翻幾行可以看到有個

public final void bindViewHolder(VH holder, int position)

,它内部調用了三參的

onBindViewHolder

關于RecyclerView.Adapter 也不是三言兩句說的清楚的。(其實我隻掌握到這裡)

好了不再跑題,回到我們的三參數的

onBindViewHolder(VH holder, int position, List<Object> payloads)

,這個方法頭部有一大堆英文注釋,我一直覺得閱讀這些英文注釋對了解方法很有用處,于是我翻譯了一下,

翻譯:

由RecyclerView調用 用來在在指定的位置顯示資料。

這個方法應該更新ViewHolder裡的ItemView的内容,以反映在給定的位置 Item(的變化)。

請注意,不像ListView,如果給定位置的item的資料集變化了,RecyclerView不會再次調用這個方法,除非item本身失效了(invalidated ) 或者新的位置不能确定。

出于這個原因,在這個方法裡,你應該隻使用 postion參數 去擷取相關的資料item,而且不應該去保持 這個資料item的副本。

如果你稍後需要這個item的position,例如設定clickListener。應該使用 ViewHolder.getAdapterPosition(),它能提供 更新後的位置。

(二筆的我看到這裡發現 這是在講解兩參的onbindViewHolder方法

下面是這個三參方法的獨特部分:)

**部分(partial)綁定**vs完整(full)綁定

payloads 參數 是一個從(notifyItemChanged(int, Object)或notifyItemRangeChanged(int, int, Object))裡得到的合并list。

如果payloads list 不為空,那麼目前綁定了舊資料的ViewHolder 和Adapter, 可以使用 payload的資料進行一次 高效的部分更新。

如果payload 是空的,Adapter必須進行一次完整綁定(調用兩參方法)。

Adapter不應該假定(想當然的認為) 在那些notifyxxxx通知方法傳遞過來的payload, 一定會在 onBindViewHolder()方法裡收到。(這一句翻譯不好 QAQ 看舉例就好)

舉例來說,當View沒有attached 在螢幕上時,這個來自notifyItemChange()的payload 就簡單的丢掉好了。

payloads對象不會為null,但是它可能是空(empty),這時候需要完整綁定(是以我們在方法裡隻要判斷isEmpty就好,不用重複判空)。

作者語:這方法是一個高效的方法。 我是個低效的翻譯者,我看了40+分鐘。才終于明白,重要的部分已經加粗顯示。

實戰:

說了這麼多話,其實用起來超級簡單:

先看如何使用getChangePayload()方法,又附帶了中英雙語注釋

/**
     * When {@link #areItemsTheSame(int, int)} returns {@code true} for two items and
     * {@link #areContentsTheSame(int, int)} returns false for them, DiffUtil
     * calls this method to get a payload about the change.
     * 
     * 當{@link #areItemsTheSame(int, int)} 傳回true,且{@link #areContentsTheSame(int, int)} 傳回false時,DiffUtils會回調此方法,
     * 去得到這個Item(有哪些)改變的payload。
     * 
     * For example, if you are using DiffUtil with {@link RecyclerView}, you can return the
     * particular field that changed in the item and your
     * {@link android.support.v7.widget.RecyclerView.ItemAnimator ItemAnimator} can use that
     * information to run the correct animation.
     * 
     * 例如,如果你用RecyclerView配合DiffUtils,你可以傳回  這個Item改變的那些字段,
     * {@link android.support.v7.widget.RecyclerView.ItemAnimator ItemAnimator} 可以用那些資訊去執行正确的動畫
     * 
     * Default implementation returns {@code null}.\
     * 預設的實作是傳回null
     *
     * @param oldItemPosition The position of the item in the old list
     * @param newItemPosition The position of the item in the new list
     * @return A payload object that represents the change between the two items.
     * 傳回 一個 代表着新老item的改變内容的 payload對象,
     */
    @Nullable
    @Override
    public Object getChangePayload(int oldItemPosition, int newItemPosition) {
        //實作這個方法 就能成為文藝青年中的文藝青年
        // 定向重新整理中的部分更新
        // 效率最高
        //隻是沒有了ItemChange的白光一閃動畫,(反正我也覺得不太重要)
        TestBean oldBean = mOldDatas.get(oldItemPosition);
        TestBean newBean = mNewDatas.get(newItemPosition);

        //這裡就不用比較核心字段了,一定相等
        Bundle payload = new Bundle();
        if (!oldBean.getDesc().equals(newBean.getDesc())) {
            payload.putString("KEY_DESC", newBean.getDesc());
        }
        if (oldBean.getPic() != newBean.getPic()) {
            payload.putInt("KEY_PIC", newBean.getPic());
        }

        if (payload.size() == )//如果沒有變化 就傳空
            return null;
        return payload;//
    }
           

簡單的說,這個方法傳回一個Object類型的payload,它包含了某個item的變化了的那些内容。

我們這裡使用Bundle儲存這些變化。

在Adapter裡如下重寫三參的

onBindViewHolder

@Override
    public void onBindViewHolder(DiffVH holder, int position, List<Object> payloads) {
        if (payloads.isEmpty()) {
            onBindViewHolder(holder, position);
        } else {
            //文藝青年中的文青
            Bundle payload = (Bundle) payloads.get();
            TestBean bean = mDatas.get(position);
            for (String key : payload.keySet()) {
                switch (key) {
                    case "KEY_DESC":
                        //這裡可以用payload裡的資料,不過data也是新的 也可以用
                        holder.tv2.setText(bean.getDesc());
                        break;
                    case "KEY_PIC":
                        holder.iv.setImageResource(payload.getInt(key));
                        break;
                    default:
                        break;
                }
            }
        }
    }
           

這裡傳遞過來的payloads是一個List,由注釋可知,一定不為null,是以我們判斷是否是empty,

如果是empty,就調用兩參的函數,進行一次Full Bind。

如果不是empty,就進行partial bind,

通過下标0取出我們在

getChangePayload

方法裡傳回的payload,然後周遊payload的key,根據key檢索,如果payload裡攜帶有相應的改變,就取出來 然後更新在ItemView上。

(這裡,通過mDatas獲得的也是最新資料源的資料,是以用payload的資料或者新資料的資料 進行更新都可以)

至此,我們已經掌握了重新整理RecyclerView,文藝青年中最文藝的那種寫法。

四 在子線程中使用DiffUtil

在DiffUtil的源碼頭部注釋中介紹了DiffUtil的相關資訊,

DiffUtil内部采用的Eugene W. Myers’s difference 算法,但該算法不能檢測移動的item,是以Google在其基礎上改進支援檢測移動項目,但是檢測移動項目,會更耗性能。

在有1000項資料,200處改動時,這個算法的耗時:

打開了移動檢測時:平均值:27.07ms,中位數:26.92ms。

關閉了移動檢測時:平均值:13.54ms,中位數:13.36ms。

有興趣可以自行去源碼頭部閱讀注釋,對我們比較有用的是其中一段提到,

如果我們的list過大,這個計算出DiffResult的時間還是蠻久的,是以我們應該将擷取DiffResult的過程放到子線程中,并在主線程中更新RecyclerView。

這裡我采用Handler配合DiffUtil使用:

代碼如下:

private static final int H_CODE_UPDATE = ;
    private List<TestBean> mNewDatas;//增加一個變量暫存newList
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case H_CODE_UPDATE:
                    //取出Result
                    DiffUtil.DiffResult diffResult = (DiffUtil.DiffResult) msg.obj;
                    diffResult.dispatchUpdatesTo(mAdapter);
                    //别忘了将新資料給Adapter
                    mDatas = mNewDatas;
                    mAdapter.setDatas(mDatas);
                    break;
            }
        }
    };
           
new Thread(new Runnable() {
                @Override
                public void run() {
                    //放在子線程中計算DiffResult
                    DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallBack(mDatas, mNewDatas), true);
                    Message message = mHandler.obtainMessage(H_CODE_UPDATE);
                    message.obj = diffResult;//obj存放DiffResult
                    message.sendToTarget();
                }
            }).start();
           

就是簡單的Handler使用,不再贅述。

五總結和其他

1 其實本文代碼量很少,可下載下傳Demo檢視,一共就四個類。

但是不知不覺又被我寫的這麼長,主要涉及到了一些源碼的注釋的翻譯,友善大家更好的了解。

2 DiffUtil很适合下拉重新整理這種場景,

更新的效率提高了,而且帶動畫,而且~還不用你動腦子算了。

不過若是就做個删除 點贊這種,完全不用DiffUtils。自己記好postion,判斷一下postion在不在螢幕裡,調用那幾個定向重新整理的方法即可。

3 其實DiffUtil不是隻能和RecyclerView.Adapter配合使用,

我們可以自己實作 ListUpdateCallback接口,利用DIffUtil幫我們找到新舊資料集的最小差異集 來做更多的事情。

4 注意 寫DEMO的時候,用于比較的新老資料集,不僅ArrayList不同,裡面每個data也要不同。 否則changed 無法觸發。

實際項目中遇不到,因為新資料往往是網絡來的。

5 今天是中秋節的最後一天,我們公司居然就開始上班了!!!氣憤之餘,我怒碼一篇DiffUtil,我都不需要用DiffUtil,也能輕易比較出我們公司和其他公司的差異。QAQ,而且今天狀态不佳,居然寫了8個小時才完工。本以為這篇文章是可以入選微作文集的,沒想到也是蠻長的。沒有耐心的其實可以下載下傳DEMO看看,代碼量沒多少,使用起來還是很輕松的。

6 關于“白光一閃”onChange動畫,

public Object getChangePayload()

這個方法傳回不為null的話,onChange采用Partial bind,就不會出現。 反之就有。

github傳送門:好用給個star呗

https://github.com/mcxtzhang/DiffUtils

CSDN傳送門:

http://download.csdn.net/detail/zxt0601/9632159