1. 程式人生 > >RecyclerView配合DiffUtil區域性重新整理完整例子

RecyclerView配合DiffUtil區域性重新整理完整例子

廢話不多說,上來就先看效果吧。點選“模擬重新整理”按鈕完成第二、三項列表中的描述文字和下面的滾動動畫的重新整理。第二次進入直接下拉到列表底部點選“模擬重新整理”按鈕,同樣可以完成定向重新整理。這應該是我們期待的效果吧。這樣的功效就是普通的RecyclerView配合DiffUtil來實現的定向區域性重新整理。

DiffUtil的用法很簡單網上隨便都有很多教程,今天主要就是放一個完整的例子出來,方便以後查閱和使用。

下面就開始介紹下這個工具類的使用步驟:

1.寫一個bean來盛放item的資料項,詳情見TestBean.java檔案:

我們的例子中每個item有四個屬性,其中id就是區分每個item重新整理前後是否是相同的item(具體在DiffCallback的areItemsTheSame方法中使用),bean的屬性如下:

 /**
     *
     * @param id   記錄每個Item的id
     * @param name item的名字
     * @param desc item的描述資訊
     * @param animText item的動畫
     */
    public TestBean(int id, String name, String desc, String animText) {
        this.itemID = id;
        this.name = name;
        this.desc = desc;
        this.textAnim = animText;
    }

2.建立舊的、新的需要重新整理的資料集合,詳情見TestMainActivity.java:

建立兩個集合模擬新舊資料集,然後把新舊資料集傳到DiffUtil.calculateDiff()方法中即可比較新舊資料集中的不同資料項。

    private List<TestBean> mDatas;  //舊的資料集合
    private List<TestBean> mNewDatas; //新的資料集合
    
        /**
         * 資料量小可以直接這麼寫,
         * 計算新舊資料集合的不同,進而根據這個不同進行更新
         */
        DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallBack(mDatas, mNewDatas), true);
        diffResult.dispatchUpdatesTo(mAdapter);
        mAdapter.setDatas(mNewDatas);

3.核心步驟:繼承DiffUtil.Callback ,實現5個方法,詳情見DiffCallBack.java,作用是將第2步中傳過來的新舊資料集進行比較,並通過getChangePayload()方法將變化的資料放到payloads引數中傳給onBindViewHolder的第三個引數,進而更新介面上的資料項;

為了拿到從上面那個DiffCallBack中傳過來的payloads引數,所以需要在DiffAdapter中重寫onBindViewHolder(DiffVH holder, int position, List<Object> payloads)三個引數的方法,拿到payloads的資料更新介面即可,詳情見DiffAdapter.java:

  • getOldListSize():舊資料集的長度。
  • getNewListSize():新資料集的長度
  • areItemsTheSame():判斷是否是同一個Item。
  • areContentsTheSame():如果是通一個Item,此方法用於判斷是否同一個 Item 的內容也相同。
  • getChangePayload():

最後一個方法的呼叫情況是:areItemsTheSame()返回true而areContentsTheSame()返回false,也就是說兩個物件代表的資料是一條,但是內容更新了。在getChangePayload()方法中,你要給出具體的變化。這裡我使用的Bundle,具體使用什麼方式來表示資料的更新並不重要,重要的是在這個方法中你把更新情況存入一個物件後,在後面還能從同一個物件中把更新的情況取出來。也就是在DiffAdapter中重寫onBindViewHolder(DiffVH holder, int position, List<Object> payloads)方法,從payloads引數中,取出來我們剛剛在新資料集合中更新的資料項。


總共三個步驟,前兩步沒有什麼好說的,就是建立新舊資料集合,並且使用現成的DiffUtil工具進行計算差集,核心是第三部中重寫的幾個方法:下面是具體程式碼,註釋很清楚了:

DiffCallBack中重寫的五個方法就是比較下新舊資料集的不同:

/**
 * 完成資料集更新的核心回撥
 * 判斷新舊Item是否相等,裡面的屬性值是否需要更新
 */

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

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

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

    //判斷是不是同一個item
    @Override
    public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
        return mOldDatas.get(oldItemPosition).getItemID() == mNewDatas.get(newItemPosition).getItemID();
    }

    //判斷Item物件裡面的屬性值是否變化了,變化了返回false
    @Override
    public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
        TestBean beanOld = mOldDatas.get(oldItemPosition);
        TestBean beanNew = mNewDatas.get(newItemPosition);
        if (!beanOld.getName().equals(beanNew.getName())) {
            return false; //如果有內容不同,就返回false
        }
        if (!beanOld.getDesc().equals(beanNew.getDesc())) {
            return false;
        }
        if (!beanOld.getTextAnim().equals(beanNew.getTextAnim())) {
           return false;
        }
        return true;
    }

    /**
     * 新值存入
     *
     * 高效的區域性更新,判斷新舊資料集每個Item裡面的屬性是否相同,不同的話將新資料集中的資料取出來,放到payLoad中
     * 然後從Adapter的onBindViewHolder(DiffVH holder, int position, List<Object> payloads)這個方法中取出更新的值,
     * 設定到相對應的Item的屬性上面,完成更新
     *
     * 呼叫情況是:areItemsTheSame()返回true而areContentsTheSame()返回false,也就是說兩個物件代表的資料是一條,但是內容更新了。
     * @param oldItemPosition
     * @param newItemPosition
     * @return
     */
    @Nullable
    @Override
    public Object getChangePayload(int oldItemPosition, int newItemPosition) {

        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.getName().equals(newBean.getName())) {
            payload.putString("KEY_NAME", newBean.getDesc());
        }
        if (!oldBean.getTextAnim().equals(newBean.getTextAnim())) {
            payload.putString("KEY_ANIM_TEXT", newBean.getTextAnim());
        }

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

在DiffAdapter中重寫onBindViewHolder的三個引數的方法就是將上面的新資料集合中比較出來的不同的資料設定到介面上,下面這個就是整個Adapter:

class DiffAdapter extends RecyclerView.Adapter<DiffAdapter.DiffVH>{

    private List<TestBean> mDatas;
    private Context mContext;
    private LayoutInflater mInflater;

    private ScaleInAnimation mSelectAnimation = new ScaleInAnimation();

    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(DiffVH holder, int position) {
        TestBean bean = mDatas.get(position);
        holder.tv1.setText(bean.getName());
        holder.tv2.setText(bean.getDesc());
        holder.switchView.startTextChangeAnim(bean.getTextAnim());
    }

    /**
     * 重寫這個方法,從DiffCallBack的getChangePayload()方法中取出存入的新值
     * @param holder
     * @param position
     * @param payloads
     */
    @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);//取出新資料來源,(可以不用,data也是新的 也可以用)
            for (String key : payload.keySet()) {
                switch (key) {
                    case "KEY_NAME":
                        //這裡可以用payload裡的資料,不過data也是新的 也可以用
                        holder.tv1.setText(bean.getName());
                        break;
                    case "KEY_DESC":
                        holder.tv2.setText(bean.getDesc());
                        break;
                    case "KEY_ANIM_TEXT":
                        holder.switchView.startTextChangeAnim(bean.getTextAnim());
                        break;
                    default:
                        break;
                }
            }
        }
    }

    @Override
    public void onViewAttachedToWindow(DiffVH holder) {
        super.onViewAttachedToWindow(holder);
        addAnimation(holder);
    }

    private void addAnimation(DiffVH holder) {
        for (Animator anim : mSelectAnimation.getAnimators(holder.itemView)) {
            anim.setDuration(300).start();
            anim.setInterpolator(new LinearInterpolator());
        }
    }

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

    class DiffVH extends RecyclerView.ViewHolder {
        TextView tv1, tv2;
        TextSwitchView switchView;

        public DiffVH(View itemView) {
            super(itemView);
            tv1 = (TextView) itemView.findViewById(R.id.tv1);
            tv2 = (TextView) itemView.findViewById(R.id.tv2);
            switchView = (TextSwitchView) itemView.findViewById(R.id.switchView);
            tv1.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(mContext,"1111111111",Toast.LENGTH_SHORT).show();
                }
            });
            tv2.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(mContext,"22222222222",Toast.LENGTH_SHORT).show();

                }
            });
        }
    }
}

上面所有的都是很常規的使用,除了public void onBindViewHolder(DiffVH holder, int position, List<Object> payloads)三個引數的重寫就是取出來新資料,設定到item上。

完整的專案地址:

注意專案裡面有4個java檔案和本文所涉及到的東西沒有關係,

TextSwitchView.java 是例子中每個Item下面的迴圈滾動文字;

BamAnim.java BamLinearLayout.java 是每個item的點選動畫效果;

ScaleAnimation.java 是每個item載入進來時候的動畫效果。這四個可以不看,或者直接刪除都是可以的。