如何通过DiffUtil工具优化RecyclerView性能?
- 内容介绍
- 文章标签
- 相关推荐
本文共计1793个文字,预计阅读时间需要8分钟。
`DiffUtil工具类应用 - 提高RecyclerView效率+问题背景+Android开发中,RecyclerView是常见的滑动列表视图组件,数据刷新时,我们经常直接调用mAdapter.notifyDataSetChanged()方法。`
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,代码如下:
(3)item对应的layout布局文件,R.layout.item_diff代码如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="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="schemas.android.com/apk/res/android" xmlns:tools="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分析: 使用新方案,当有数据需要刷新时,刷新数据的效率更高,并且数据的刷新过程可以看到有意思的动画,有兴趣可以实操感受下。
本文共计1793个文字,预计阅读时间需要8分钟。
`DiffUtil工具类应用 - 提高RecyclerView效率+问题背景+Android开发中,RecyclerView是常见的滑动列表视图组件,数据刷新时,我们经常直接调用mAdapter.notifyDataSetChanged()方法。`
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,代码如下:
(3)item对应的layout布局文件,R.layout.item_diff代码如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="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="schemas.android.com/apk/res/android" xmlns:tools="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分析: 使用新方案,当有数据需要刷新时,刷新数据的效率更高,并且数据的刷新过程可以看到有意思的动画,有兴趣可以实操感受下。

