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也会提示
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