DiffUtil 配合RecyclerView.Adapter使

作者: sirai | 来源:发表于2016-10-11 17:38 被阅读552次
    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

    相关文章

      网友评论

        本文标题:DiffUtil 配合RecyclerView.Adapter使

        本文链接:https://www.haomeiwen.com/subject/qajbyttx.html