RecyclerView扩展知识: DiffUtil_diffutil.itemcallback-程序员宅基地

技术标签: DiffUtil  android  RecyclerView  

1.简介

DiffUtil是support-v7:24.2.0中的新工具类,它用来比较两个数据集,寻找出旧数据集-》新数据集的最小变化量,定向刷新列表。

最大的用处就是在RecyclerView刷新时,不再使用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);

2. 例子

Bean:

public class TestBean {
    
    private int id;
    private String name;

    public TestBean(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
DiffUtil.Callback: 用来比较数据集的差异
public class MyDiffUtilCallback extends DiffUtil.Callback{

    private List<TestBean> mOldItems;
    private List<TestBean> mNewItems;

    public void setItems(@NonNull final List<TestBean> oldItems, @NonNull final List<TestBean> newItems) {
        mOldItems = oldItems;
        mNewItems = newItems;
    }

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

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

    /**
     * 是否是同一个对象
     */
    @Override
    public boolean areItemsTheSame(final int oldItemPosition, final int newItemPosition) {
        if (mOldItems.get(oldItemPosition) == null || mNewItems.get(newItemPosition) == null){
            return false;
        }
        return mOldItems.get(oldItemPosition).getId() == mNewItems.get(newItemPosition).getId();
    }
    
    /**
     * 是否是相同内容
     */
    @Override
    public boolean areContentsTheSame(final int oldItemPosition, final int newItemPosition) {
        return mOldItems.get(oldItemPosition).getName().equals(mNewItems.get(newItemPosition).getName());
    }

    /**
     * areItemsTheSame()返回true而areContentsTheSame()返回false时调用,也就是说两个对象代表的数据是一条,但是内容更新了。
     */
    @Nullable
    @Override
    public Object getChangePayload(final int oldItemPosition, final int newItemPosition) {
        TestBean oldBean = mOldItems.get(oldItemPosition);
        TestBean newBean = mNewItems.get(newItemPosition);

        //这里就不用比较核心字段了,一定相等
        Bundle payload = new Bundle();
       
        if (!oldBean.getName().equals(newBean.getName())) {
            payload.putString("KEY_NAME", newBean.getName());
        }

        if (payload.size() == 0){
            //如果没有变化 就传空
            return null;
        }
        return payload;
    }
   
}
DiffUtilAdapter:
public class DiffUtilAdapter extends RecyclerView.Adapter<DiffUtilAdapter.ViewHolder> {
   
    private List<TestBean> mList = new ArrayList();
    private LayoutInflater mInflater;
    private MyDiffUtilCallback mDiffCallback;
    
    public DiffUtilAdapter(Context mContext) {
        mDiffCallback = new MyDiffUtilCallback();
        mInflater = LayoutInflater.from(mContext);
    }

    public void setData(TestBean mData){
        mList.add(mData);
        notifyItemRangeInserted(getItemCount(), 1);
    }

    public void setData(List<TestBean> mData){
        mDiffCallback.setItems(mList, mData);
        DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(mDiffCallback);
        diffResult.dispatchUpdatesTo(this);
        mList.clear();
        mList.addAll(mData);
    }

    public void removeData(int index){
        mList.remove(index);
        notifyItemRemoved(index);
        if (index != mList.size()) {
            notifyItemRangeChanged(index, mList.size() - index);
        }
    }
    
    public void clear(){
        mList.clear();
        notifyDataSetChanged();
    }
    
    @Override
    @NonNull
    public DiffUtilAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new ViewHolder(mInflater.inflate(R.layout.item_test, parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) {
        if (payloads.isEmpty()) {
            onBindViewHolder(holder, position);
        } else {
            Bundle bundle = (Bundle) payloads.get(0);
            holder.mTvName.setText(bundle.getString("KEY_NAME"));
        }
    }

    @Override
    public void onBindViewHolder(@NonNull DiffUtilAdapter.ViewHolder holder, final int position) {
        TestBean bean = mList.get(position);
        holder.mTvName.setText(bean.getName());
    }
    
    @Override
    public int getItemCount() {
        return mList.size();
    }

    static class ViewHolder extends RecyclerView.ViewHolder {

        TextView mTvName;
        
        ViewHolder(View itemView) {
            super(itemView);
            mTvName = itemView.findViewById(R.id.tv_name);
        }
    }
}

Activity:

public class DiffUtilActivity extends AppCompatActivity {

    private DiffUtilAdapter mDiffUtilAdapter;
    private int count = 10;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sorted_list);
        RecyclerView mRecyclerView = findViewById(R.id.rv);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mDiffUtilAdapter = new DiffUtilAdapter(this);
        mRecyclerView.setAdapter(mDiffUtilAdapter);
        initData();
    }

    private void addData() {
        mDiffUtilAdapter.setData(new TestBean(count, "Item " + count));
        count ++;
    }

    private List<TestBean> mList = new ArrayList();

    private void initData() {
        mList.clear();
        for (int i = 0; i < 10; i++){
            mList.add(new TestBean(i, "Item " + i));
        }
        mDiffUtilAdapter.setData(mList);
    }

    private void updateData() {
        mList.clear();
        for (int i = 9; i >= 0; i--){
            mList.add(new TestBean(i, "Item " + i));
        }
        mDiffUtilAdapter.setData(mList);
    }
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu, menu);
        return true;
    }
    
    private Random mRandom = new Random();

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int i = item.getItemId();
        if (i == R.id.menu_add) {
            addData();
        } else if (i == R.id.menu_update) {
            updateData();
        } else if (i == R.id.menu_delete) {
            if (mDiffUtilAdapter.getItemCount() > 0){
                mDiffUtilAdapter.removeData(mRandom.nextInt(mDiffUtilAdapter.getItemCount()));
            }
        }else if (i == R.id.menu_clear){
            mDiffUtilAdapter.clear();
        }
        return true;
    }
}

3.AsyncListDiffer

不过DiffUtil的问题在于计算数据差异DiffUtil.calculateDiff(mDiffCallback)时是一个耗时操作,需要我们放到子线程去处理,最后在主线程刷新。为了方便这一操作,在support-v7:27.1.0又新增了一个DiffUtil的封装类,那就是AsyncListDiffer。

3.1 首先实现DiffUtil.ItemCallback

public class MyDiffUtilItemCallback extends DiffUtil.ItemCallback<TestBean> {

     /**
     * 是否是同一个对象
     */  
    @Override
    public boolean areItemsTheSame(@NonNull TestBean oldItem, @NonNull TestBean newItem) {
        return oldItem.getId() == newItem.getId();
    }
     /**
     * 是否是相同内容
     */ 
    @Override
    public boolean areContentsTheSame(@NonNull TestBean oldItem, @NonNull TestBean newItem) {
        return oldItem.getName().equals(newItem.getName());
    }

    /**
     * areItemsTheSame()返回true而areContentsTheSame()返回false时调用,也就是说两个对象代表的数据是一条,但是内容更新了。此方法为定向刷新使用,可选。
     */
    @Nullable
    @Override
    public Object getChangePayload(@NonNull TestBean oldItem, @NonNull TestBean newItem) {
        Bundle payload = new Bundle();

        if (!oldItem.getName().equals(newItem.getName())) {
            payload.putString("KEY_NAME", newItem.getName());
        }

        if (payload.size() == 0){
            //如果没有变化 就传空
            return null;
        }
        return payload;
    }
}

3.2 实现实现RecyclerView.Adapter

public class AsyncListDifferAdapter extends RecyclerView.Adapter<AsyncListDifferAdapter.ViewHolder> {

    private LayoutInflater mInflater;
    // 数据的操作由AsyncListDiffer实现
    private AsyncListDiffer<TestBean> mDiffer;

    public AsyncListDifferAdapter(Context mContext) {
        // 初始化AsyncListDiffe
        mDiffer = new AsyncListDiffer<>(this, new MyDiffUtilItemCallback());
        mInflater = LayoutInflater.from(mContext);
    }

    public void setData(TestBean mData){
        List<TestBean> mList = new ArrayList<>();
        mList.addAll(mDiffer.getCurrentList());
        mList.add(mData);
        mDiffer.submitList(mList);
    }

    public void setData(List<TestBean> mData){
        // 由于DiffUtil是对比新旧数据,所以需要创建新的集合来存放新数据。
        // 实际情况下,每次都是重新获取的新数据,所以无需这步。
        List<TestBean> mList = new ArrayList<>();
        mList.addAll(mData);
        mDiffer.submitList(mList);
    }

    public void removeData(int index){
        List<TestBean> mList = new ArrayList<>();
        mList.addAll(mDiffer.getCurrentList());
        mList.remove(index);
        mDiffer.submitList(mList);
    }

    public void clear(){
        mDiffer.submitList(null);
    }

    @Override
    @NonNull
    public AsyncListDifferAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new ViewHolder(mInflater.inflate(R.layout.item_test, parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) {
        if (payloads.isEmpty()) {
            onBindViewHolder(holder, position);
        } else {
            Bundle bundle = (Bundle) payloads.get(0);
            holder.mTvName.setText(bundle.getString("KEY_NAME"));
        }
    }

    @Override
    public void onBindViewHolder(@NonNull AsyncListDifferAdapter.ViewHolder holder, final int position) {
        TestBean bean = mDiffer.getCurrentList().get(position);
        holder.mTvName.setText(bean.getName());
    }

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

    static class ViewHolder extends RecyclerView.ViewHolder {

       ......
    }
}

Activity:

public class AsyncListDifferActivity extends AppCompatActivity {

    private AsyncListDifferAdapter mAsyncListDifferAdapter;
    private int count = 10;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sorted_list);
        RecyclerView mRecyclerView = findViewById(R.id.rv);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mAsyncListDifferAdapter = new AsyncListDifferAdapter(this);
        mRecyclerView.setAdapter(mAsyncListDifferAdapter);
        initData();
    }

    private void addData() {
        mAsyncListDifferAdapter.setData(new TestBean(count, "Item " + count));
        count ++;
    }

    private List<TestBean> mList = new ArrayList();

    private void initData() {
        mList.clear();
        for (int i = 0; i < 10; i++){
            mList.add(new TestBean(i, "Item " + i));
        }
        mAsyncListDifferAdapter.setData(mList);
    }

    private void updateData() {
        mList.clear();
        for (int i = 9; i >= 0; i--){
            mList.add(new TestBean(i, "Item " + i));
        }
        mAsyncListDifferAdapter.setData(mList);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu, menu);
        return true;
    }

    private Random mRandom = new Random();

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int i = item.getItemId();
        if (i == R.id.menu_add) {
            addData();
        } else if (i == R.id.menu_update) {
            updateData();
        } else if (i == R.id.menu_delete) {
            if (mAsyncListDifferAdapter.getItemCount() > 0){
                mAsyncListDifferAdapter.removeData(mRandom.nextInt(mAsyncListDifferAdapter.getItemCount()));
            }
        }else if (i == R.id.menu_clear){
            mAsyncListDifferAdapter.clear();
        }
        return true;
    }
}

我们简单的看一下AsyncListDiffer 的 submitList源码:

public void submitList(@Nullable final List<T> newList) {
        final int runGeneration = ++this.mMaxScheduledGeneration;
        if (newList != this.mList) {
            if (newList == null) {
                // 新数据为null时清空列表
                int countRemoved = this.mList.size();
                this.mList = null;
                this.mReadOnlyList = Collections.emptyList();
                this.mUpdateCallback.onRemoved(0, countRemoved);
            } else if (this.mList == null) {
                // 旧数据为null时添加数据
                this.mList = newList;
                this.mReadOnlyList = Collections.unmodifiableList(newList);
                this.mUpdateCallback.onInserted(0, newList.size());
            } else {
                final List<T> oldList = this.mList;
                // 计算数据差异放在子线程
                this.mConfig.getBackgroundThreadExecutor().execute(new Runnable() {
                    public void run() {
                        final DiffResult result = DiffUtil.calculateDiff(new Callback() {
                           ...
                        });
                        // 主线程刷新列表
                        AsyncListDiffer.this.mMainThreadExecutor.execute(new Runnable() {
                            public void run() {
                                if (AsyncListDiffer.this.mMaxScheduledGeneration == runGeneration) {
                                    AsyncListDiffer.this.latchList(newList, result);
                                }

                            }
                        });
                    }
                });
            }
        }
    }


void latchList(@NonNull List<T> newList, @NonNull DiffResult diffResult) {
     this.mList = newList;
     this.mReadOnlyList = Collections.unmodifiableList(newList);
     // 熟悉的dispatchUpdatesTo方法
     diffResult.dispatchUpdatesTo(this.mUpdateCallback);
}

AsyncListDiffer就是在这里帮我们做了线程的处理。方便我们正确规范的使用。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/jxf_access/article/details/86512924

智能推荐

【技术教程】RTSP/GB28181协议视频平台对接RTMP推流摄像头编码简介_rtsp编解码视频教程-程序员宅基地

文章浏览阅读642次。视频是利用人眼视觉暂留的原理,通过播放一系列的图片,使人眼产生运动的感觉。单纯传输视频画面,视频量非常大,对现有的网络和存储来说是不可接受的。为了能够使视频便于传输和存储,人们发现视频有大量重复的信息,如果将重复信息在发送端去掉,在接收端恢复出来,这样就大大减少了视频数据的文件,因此有了H.264视频压缩标准。在H.264压缩标准中I帧、P帧、B帧用于表示传输的视频画面。1.I帧I帧又称帧内编码帧,是一种自带全部信息的独立帧,无需参考其他图像便可独立进行解码,可以简单理解为一张静态画面。视频_rtsp编解码视频教程

svn—Eclipse中如何显示svn 信息_eclipse怎么显示项目文件是svn哪个分支-程序员宅基地

文章浏览阅读6.4k次。从svn下载的项目,导入Eclipse中,就是不显示出文件的文件的状态图标和后面的版本号。_eclipse怎么显示项目文件是svn哪个分支

pycharm修改文件默认保存路径,pycharm设置背景颜色_pycharm字体颜色设置文件保存在哪里-程序员宅基地

文章浏览阅读2.1k次。pycharm修改背景颜色(修改默认保存路径见下一张图)_pycharm字体颜色设置文件保存在哪里

document.createElement()只能声明一个标签不能添加属性 借助以下函数完善功能_document.createtextnode 单标签问题-程序员宅基地

文章浏览阅读718次。document.createElement()只能声明一个标签不能添加属性 借助以下函数完善功能添加子节点 function elt(tagName, ...children) { //children为所添加的子节点 for (var child of children) { if (typeof child === 'string') { //判断是否是文本节点 node.appendChild(document.create_document.createtextnode 单标签问题

Table 交换行tr标签_table tr换行-程序员宅基地

文章浏览阅读593次。CoffeeTeaMilk Coffee1 Tea2 Milk3 Fruit4 window.onload = function(){ var textnode =document.createTextNode("1111"); var item=document.getElementById("myList")._table tr换行

C++与QML逻辑分离_qml c++ 业务分离-程序员宅基地

文章浏览阅读658次。最近在项目中,用户提出我们需要使用QML开发项目界面,并且不需要我们实现C++底层逻辑,只需要把接口暴露出来供调用。我尝试过构想用信号槽机制来实现交互,但是总感觉最后出来的程序会有一大堆的信号和槽函数,很不优雅,并且不易于维护。所以就尝试用其他方法来实现。为了方便大家理解,我写了一个登录的Demo,Demo的目录结构如下:implements目录下包含了LoginImplements.js..._qml c++ 业务分离

随便推点

jQuery Validate验证方法及教程-程序员宅基地

文章浏览阅读58次。//实名认证 验证$(function(){ //中文姓名验证 jQuery.validator.addMethod("zh_verify", function(value, element) { var tel = /^[\u4E00-\u9FA5\uf900-\ufa2d]{2,10}$/; return this.optional(..._jquery validate教程

springboot集成elastic job_@elasticjobconf-程序员宅基地

文章浏览阅读806次。pom.xml添加依赖<dependency> <groupId>com.cxytiandi</groupId> <artifactId>elastic-job-spring-boot-starter</artifactId> <version>1.0.0</version></dependency><dependency> <groupId>org.apache.curat_@elasticjobconf

TensorFlow实现图像风格迁移_使用tensorflow实现图像风格迁移教程-程序员宅基地

文章浏览阅读2k次。@Author:Runsen来源: https://colab.research.google.com/drive/10p0UvG1wI1wWoKnc2chPXRA_-a44_lMU图像风格迁移一般指的是把图片特征中的风格部分迁移应用到目标图片的过程。整个迁移处理的输入分别有内容图和风格图,输出就是风格迁移后的结果图。应用图像风格迁移后,可以生成相同风格的图片。下面是内容图下面是风格图该案例来源: https://colab.research.google.com/driv_使用tensorflow实现图像风格迁移教程

java 把对象转成map_Java对象转换成Map-程序员宅基地

文章浏览阅读4.5k次。需求总是千奇百怪,对象转成map,看似没必要,但就是有这个需求,所以记录下来首先是beanpackage tools;import lombok.data;/*** 车辆实体类*/@datapublic class car {private string id;private string model;//型号private string color;//颜色private string volu..._把对象转成map

同是程序员凭什么你的薪资就比别人低?阿里Java“涨薪秘籍”首次公开,差距不止一点点!_程序员招聘网站上的要求都很高工资却不高-程序员宅基地

文章浏览阅读265次,点赞7次,收藏3次。前言可能有些人会常常有这样的感觉,同是开发有些人比我工资高却什么代码都不写呢?当我听到这个问题的时候第一次映入脑海的就是:工程师的分类。大家可以来看看Java工程师在招聘网站上的区分:初中级开发工程师一般的初中级开发工程师要求不是特别的高,很多都是要求你会在公司干活,然后能够对公司项目进行代码的编写,和业务的实现。一般要求熟悉 Spring boot,Spring等框架;熟悉dubbo框架、redis等; 熟悉Unix/Linux系统,精通数据库Oracle、MySQL 等的开发,精通SQL及_程序员招聘网站上的要求都很高工资却不高

SQL:如何在建表时创建一个13位的时间戳字段_sql 13位时间戳-程序员宅基地

文章浏览阅读1.3k次。知识预备:- 1、 INT - 正常大小的整数,可以带符号。如果是有符号的,它允许的范围是从-2147483648到2147483647。如果是无符号,允许的范围是从0到4294967295;- 2、BIGINT - 一个大的整数,可以带符号。如果有符号,允许范围为-9223372036854775808到9223372036854775807。如果无符号,允许的范围是从0到18446744073709551615。则13位时间戳应选用bignit- 3、current_timestamp()时间戳_sql 13位时间戳

推荐文章

热门文章

相关标签