DiffUtil 配合RecyclerView.Adapter使用

DiffUtils的效果圖,最明顯的是有插入、移動Item的動畫.gif

一個新的工具類誕生了 DiffUtil 今天初學了一番

簡介

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

  • 區別

    (1) 它最大的用處就是在RecyclerView刷新時,不再使用mAdapter.notifyDataSetChanged()。

    mAdapter.notifyDataSetChanged()有兩個缺點:

    • 不會觸發RecyclerView的動畫(刪除、新增、位移、change動畫)
    • 性能較低,畢竟是無腦的刷新了一遍整個RecyclerView , 極端情況下:新老數據集一模一樣,效率是最低的。

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

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

  • adapter.notifyItemRangeChanged(position, count, payload);

學習點

  • DiffUtil的簡單用法,實現刷新時的“增量更新”效果
  • DiffUtil的高級用法,在某項Item只有內容(data)變化,位置(position) 未變化時,完成部分更新(官方稱之為Partial bind,部分綁定)。
  • 在子線程中計算DiffResult,在主線程中刷新RecyclerView。
  • DiffUtil部分類、方法 官方注釋的漢化

簡單用法

  • activity 代碼
    將獲取DiffResult的過程放到子線程中,并在主線程中更新RecyclerView。
    這里我采用Handler配合DiffUtil使用
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", "PHP", R.drawable.pic3));
        mDatas.add(new TestBean("檸檬4", "C", R.drawable.pic4));
        mDatas.add(new TestBean("檸檬5", "IOS", R.drawable.pic5));
    }

    /**
     * 模擬刷新操作
     *
     * @param view
     */
    public void onRefresh(View view) {
        try {
            mNewDatas = new ArrayList<>();
            for (TestBean bean : mDatas) {
                mNewDatas.add(bean.clone());//clone一遍舊數據 ,模擬刷新操作
            }
            mNewDatas.add(new TestBean("SIRAI", "DiffUtil", R.drawable.pic2));//模擬新增數據
            mNewDatas.get(0).setDesc("refresh+");
            mNewDatas.get(0).setPic(R.drawable.pic1);//模擬修改數據
            TestBean testBean = mNewDatas.get(1);//模擬數據位移
            mNewDatas.remove(testBean);
            mNewDatas.add(testBean);

            //新寵
            //利用DiffUtil.calculateDiff()方法,傳入一個規則DiffUtil.Callback對象,和是否檢測移動item的 boolean變量,得到DiffUtil.DiffResult 的對象
            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();
            //mAdapter.notifyDataSetChanged();//以前普通青年的我們只能這樣,現在我們是文藝青年了,有新寵了

        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }

    private static final int H_CODE_UPDATE = 1;
    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;
                    //利用DiffUtil.DiffResult對象的dispatchUpdatesTo()方法,傳入RecyclerView的Adapter,輕松成為文藝青年
                    diffResult.dispatchUpdatesTo(mAdapter);
                    //別忘了將新數據給Adapter
                    mDatas = mNewDatas;
                    mAdapter.setDatas(mDatas);
                    break;
            }
        }
    };

}
  • DiffCallBack
    實現一個繼承自DiffUtil.Callback的類,實現它的四個abstract方法。
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() : 0;
    }

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

    /**
     * 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內容是相同的
    }

    /**
     * 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.
     * <p>
     * 當{@link #areItemsTheSame(int, int)} 返回true,且{@link #areContentsTheSame(int, int)} 返回false時,DiffUtils會回調此方法,
     * 去得到這個Item(有哪些)改變的payload。
     * <p>
     * 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.
     * <p>
     * 例如,如果你用RecyclerView配合DiffUtils,你可以返回  這個Item改變的那些字段,
     * {@link android.support.v7.widget.RecyclerView.ItemAnimator ItemAnimator} 可以用那些信息去執行正確的動畫
     * <p>
     * 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() == 0)//如果沒有變化 就傳空
            return null;
        return payload;//
    }
}
  • DiffAdapter
    亮點:onBindViewHolder(DiffVH holder, int position, List<Object> payloads)
public class DiffAdapter extends RecyclerView.Adapter<DiffAdapter.DiffVH> {
    private final static String TAG = "sirai";
    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 void onBindViewHolder(DiffVH holder, int position, List<Object> payloads) {
        if (payloads.isEmpty()) {
            onBindViewHolder(holder, position);
        } else {
            //文藝青年中的文青
            Bundle payload = (Bundle) payloads.get(0);//取出我們在getChangePayload()方法返回的bundle
            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;
                }
            }
        }
    }

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

    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);
        }
    }
}

github地址 https://github.com/mcxtzhang/DiffUtils

歡迎加入QQ群:104286694

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 簡介: 提供一個讓有限的窗口變成一個大數據集的靈活視圖。 術語表: Adapter:RecyclerView的子類...
    酷泡泡閱讀 5,212評論 0 16
  • 背景 一年多以前我在知乎上答了有關LeetCode的問題, 分享了一些自己做題目的經驗。 張土汪:刷leetcod...
    土汪閱讀 12,769評論 0 33
  • 初識Android時,我對ListView、GradView中的Adapter一直半懂非懂,每次寫Adapter都...
    blink_dagger閱讀 6,013評論 4 10
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,923評論 18 139
  • 最近買了很多有關文案策劃以及微信運營方面的書來看,是想從專業技能方向有所整體方向的認知,雖然之前接觸過微信運營,編...
    影像紀錄閱讀 690評論 0 3