首页 > 其他分享 >DiffUtil工具类使用-让recyclerview使用更高效

DiffUtil工具类使用-让recyclerview使用更高效

时间:2022-10-24 16:34:12浏览次数:57  
标签:使用 MyBean new newDatas import DiffUtil mDatas recyclerview public

DiffUtil工具类使用-让recyclerview使用更高效

问题背景

安卓开发过程中,recyclerview是很常见的滑动列表视图组件,数据刷新的时候,我们经常就是直接调用了mAdapter.notifyDataSetChanged()的方法进行操作。但是很显然,这样直接操作有两个问题: (1)不会触发RecyclerView的动画效果(删除、新增、位移、change动画) (2)性能较低,毕竟是无脑的刷新了一遍整个RecyclerView。

问题分析

这时候,就有一个好用的工具类登场了。DiffUtil是support-v7:24.2.0中的新工具类,它用来比较两个数据集,寻找出旧数据集到新数据集的最小变化量。使用DiffUtil后,改为如下代码:

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

它会自动计算新老数据集的差异,并根据差异情况,调用以下四个方法:

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

显然,这个四个方法在执行时都是伴有RecyclerView的动画的,且都是定向刷新方法,效率明显是会提升不少。

实践demo

(1)新建一个JavaBean类,列表item的数据,代码如下:

class MyBean implements Cloneable {
    private String name;
    private String desc;
    private int pic;


    @Override
    public MyBean clone() throws CloneNotSupportedException {
        MyBean bean = null;
        try {
            bean = (MyBean) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return bean;
    }

    public MyBean(String name, String desc, int pic) {
        this.name = name;
        this.desc = desc;
        this.pic = pic;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public int getPic() {
        return pic;
    }

    public void setPic(int pic) {
        this.pic = pic;
    }
}

(2)实现recyclerview对应的adapter,代码如下:

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

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

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

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

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

    @Override
    public void onBindViewHolder(final DiffViewHolder holder, final int position) {
        MyBean 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() : 0;
    }

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

        public DiffViewHolder(View itemView) {
            super(itemView);
            tv1 = itemView.findViewById(R.id.text1);
            tv2 = itemView.findViewById(R.id.text2);
            iv = itemView.findViewById(R.id.img1);
        }
    }
}

(3)item对应的layout布局文件,R.layout.item_diff代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:orientation="horizontal"
    android:layout_height="wrap_content">
    <TextView
        android:id="@+id/text1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <TextView
        android:id="@+id/text2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <ImageView
        android:id="@+id/img1"
        android:layout_width="50dp"
        android:layout_height="wrap_content"/>
</LinearLayout>

(4)新建activity代码如下:

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.os.Bundle;
import android.view.View;

import java.util.ArrayList;
import java.util.List;

public class DiffUtilActivity extends AppCompatActivity {
    private List<MyBean> mDatas;
    private RecyclerView mRv;
    private DiffAdapter mAdapter;

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

    private void initData() {
        mDatas = new ArrayList<>();
        mDatas.add(new MyBean("测试1", "Android", R.drawable.apple));
        mDatas.add(new MyBean("测试2", "Java", R.drawable.ball));
        mDatas.add(new MyBean("测试3", "C", R.drawable.tao));
        mDatas.add(new MyBean("测试4", "PHP", R.drawable.apple));
        mDatas.add(new MyBean("测试5", "Python", R.drawable.ball));
    }

    /**
     * 模拟刷新操作
     *
     * @param view
     */
    public void onRefresh(View view) {
        try {
            // 模拟数据更新
            List<MyBean> newDatas = new ArrayList<>();
            for (MyBean bean : mDatas) {
                newDatas.add(bean.clone());
            }
            newDatas.add(new MyBean("李小龙", "帅", R.drawable.tao));
            newDatas.get(0).setDesc("Android+");
            MyBean myBean = newDatas.get(1);
            newDatas.remove(myBean);
            newDatas.add(myBean);
            //别忘了将新数据给Adapter
            mDatas = newDatas;
            mAdapter.setDatas(mDatas);
            // 通知数据变化刷新
            mAdapter.notifyDataSetChanged();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

(5)activity对应的layout布局文件如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity.diffUtilTest.DiffUtilActivity"
    android:orientation="vertical"
    tools:ignore="MissingDefaultResource">

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <LinearLayout
            android:layout_width="match_parent"
            android:orientation="vertical"
            android:layout_height="wrap_content">
            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/recyclerView1"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>
            <Button
                android:id="@+id/btnRefresh"
                android:text="模拟刷新"
                android:onClick="onRefresh"
                android:layout_gravity="center_horizontal"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
        </LinearLayout>
    </ScrollView>
</LinearLayout>

demo分析 我们在activity中模拟数据修改,通知recyclerview适配数据修改,使用的方法是:

            // 新数据给Adapter
            mDatas = newDatas;
            mAdapter.setDatas(mDatas);
            // 通知数据变化刷新
            mAdapter.notifyDataSetChanged();

这个方法,基本属于全局重新刷新数据,Android studio也会提示 image.png

DiffUtil优化方案

(1)新建我们自己的DiffUtil.Callback,代码如下:

import androidx.recyclerview.widget.DiffUtil;
import java.util.List;

public class DiffUtilCallBack extends DiffUtil.Callback {
    private List<MyBean> mOldDatas;
    private List<MyBean> mNewDatas;

    public DiffUtilCallBack(List<MyBean> mOldDatas, List<MyBean> 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;
    }

    @Override
    public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
        return mOldDatas.get(oldItemPosition).getName().equals(mNewDatas.get(newItemPosition).getName());
    }

    @Override
    public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
        MyBean beanOld = mOldDatas.get(oldItemPosition);
        MyBean beanNew = mNewDatas.get(newItemPosition);
        if (!beanOld.getDesc().equals(beanNew.getDesc())) {
            // 如果有内容不同,就返回false
            return false;
        }
        if (beanOld.getPic() != beanNew.getPic()) {
            // 如果有内容不同,就返回false
            return false;
        }
        // 默认两个data内容是相同的
        return true;
    }
}


(2)activity中修改数据刷新方法,代码如下:

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.os.Bundle;
import android.view.View;

import java.util.ArrayList;
import java.util.List;

public class DiffUtilActivity extends AppCompatActivity {
    private List<MyBean> mDatas;
    private RecyclerView mRv;
    private DiffAdapter mAdapter;

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

    private void initData() {
        mDatas = new ArrayList<>();
        mDatas.add(new MyBean("测试1", "Android", R.drawable.apple));
        mDatas.add(new MyBean("测试2", "Java", R.drawable.ball));
        mDatas.add(new MyBean("测试3", "C", R.drawable.tao));
        mDatas.add(new MyBean("测试4", "PHP", R.drawable.apple));
        mDatas.add(new MyBean("测试5", "Python", R.drawable.ball));
    }

    /**
     * 模拟刷新操作
     *
     * @param view
     */
    public void onRefresh(View view) {
        try {
            // 模拟数据更新
            List<MyBean> newDatas = new ArrayList<>();
            for (MyBean bean : mDatas) {
                newDatas.add(bean.clone());
            }
            newDatas.add(new MyBean("李小龙", "帅", R.drawable.tao));
            newDatas.get(0).setDesc("Android+");
            MyBean myBean = newDatas.get(1);
            newDatas.remove(myBean);
            newDatas.add(myBean);
            // 1、之前的方案*******************************************
//            // 新数据给Adapter
//            mDatas = newDatas;
//            mAdapter.setDatas(mDatas);
//            // 通知数据变化刷新
//            mAdapter.notifyDataSetChanged();

            ///2、DiffUtil的方式通知数据刷新******************************
            DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtilCallBack(mDatas, newDatas), true);
            diffResult.dispatchUpdatesTo(mAdapter);
            mDatas = newDatas;
            mAdapter.setDatas(mDatas);
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

demo分析: 使用新方案,当有数据需要刷新时,刷新数据的效率更高,并且数据的刷新过程可以看到有意思的动画,有兴趣可以实操感受下。

标签:使用,MyBean,new,newDatas,import,DiffUtil,mDatas,recyclerview,public
From: https://blog.51cto.com/baorant24/5790245

相关文章