DiffUtil工具类使用-让recyclerview使用更高效
问题背景
安卓开发过程中,recyclerview是很常见的滑动列表视图组件,数据刷新的时候,我们经常就是直接调用了mAdapter.notifyDataSetChanged()的方法进行操作。但是很显然,这样直接操作有两个问题: (1)不会触发RecyclerView的动画效果(删除、新增、位移、change动画) (2)性能较低,毕竟是无脑的刷新了一遍整个RecyclerView。
问题分析
这时候,就有一个好用的工具类登场了。DiffUtil是support-v7:24.2.0中的新工具类,它用来比较两个数据集,寻找出旧数据集到新数据集的最小变化量。使用DiffUtil后,改为如下代码:
// 新方案
val diffResult = DiffUtil.calculateDiff(DiffUtilCallBack(oldData, mDatas), 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(var name: String, var desc: String, var pic: Int) :
Cloneable {
@Throws(CloneNotSupportedException::class)
public override fun clone(): MyBean {
var bean: MyBean? = null
try {
bean = super.clone() as MyBean
} catch (e: CloneNotSupportedException) {
e.printStackTrace()
}
return bean!!
}
}
(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 composer.model.MyBean
class DiffAdapter(private val mContext: Context, mDatas: List?) :
RecyclerView.Adapter() {
private var mDatas: List?
private val mInflater: LayoutInflater
init {
this.mDatas = mDatas
mInflater = LayoutInflater.from(mContext)
}
fun setDatas(mDatas: List?) {
this.mDatas = mDatas
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DiffViewHolder {
return DiffViewHolder(mInflater.inflate(mContext.resources.getLayout(R.layout.item_diff1), parent, false))
}
override fun onBindViewHolder(holder: DiffViewHolder, position: Int) {
val bean: MyBean = mDatas!![position]
holder.tv1.text = bean.name
holder.tv2.text = bean.desc
holder.iv.setImageResource(bean.pic)
}
override fun getItemCount(): Int {
return if (mDatas != null) mDatas!!.size else 0
}
inner class DiffViewHolder(itemView: View) :
RecyclerView.ViewHolder(itemView) {
var tv1: TextView
var tv2: TextView
var iv: ImageView
init {
tv1 = itemView.findViewById(R.id.text1)
tv2 = itemView.findViewById(R.id.text2)
iv = itemView.findViewById(R.id.img1)
}
}
companion object {
private const val TAG = "DiffAdapter"
}
}
(3)item对应的layout布局文件,R.layout.item_diff代码如下:
(4)新建activity代码如下:
package composer
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import composer.adapter.DiffAdapter
import composer.model.MyBean
class DiffUtilActivity : AppCompatActivity() {
private var mDatas: MutableList? = null
private var mRv: RecyclerView? = null
private var mAdapter: DiffAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_diff_util)
initData()
mRv = findViewById(R.id.recyclerView1)
mRv?.layoutManager = LinearLayoutManager(this)
mAdapter = DiffAdapter(this, mDatas)
mRv?.adapter = mAdapter
}
private fun initData() {
mDatas = mutableListOf()
mDatas?.add(MyBean("测试1", "Android", R.drawable.bg_ranking))
mDatas?.add(MyBean("测试2", "Java", R.drawable.ic_pic_add))
mDatas?.add(MyBean("测试3", "C", R.drawable.abc_vector_test))
mDatas?.add(MyBean("测试4", "PHP", R.drawable.bg_daily_btn))
mDatas?.add(MyBean("测试5", "Python", R.drawable.bg_live_top))
}
/**
* 模拟刷新操作
*
* @param view
*/
fun onRefresh(view: View?) {
try {
mDatas?.add(MyBean("李小龙", "帅", R.drawable.bg_live_bottom))
val myBean = mDatas?.removeAt(1)
if (myBean != null) {
mDatas?.add(myBean)
}
mAdapter?.notifyDataSetChanged()
} catch (e: CloneNotSupportedException) {
e.printStackTrace()
}
}
}
(5)activity对应的layout布局文件如下:
demo分析 我们在activity中模拟数据修改,通知recyclerview适配数据修改,使用的方法是:
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 mOldDatas;
private List mNewDatas;
public DiffUtilCallBack(List mOldDatas, List 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中修改数据刷新方法,代码如下:
package composer
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import composer.adapter.DiffAdapter
import composer.callback.DiffUtilCallBack
import composer.model.MyBean
class DiffUtilActivity : AppCompatActivity() {
private var mDatas: MutableList? = null
private var mRv: RecyclerView? = null
private var mAdapter: DiffAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_diff_util)
initData()
mRv = findViewById(R.id.recyclerView1)
mRv?.layoutManager = LinearLayoutManager(this)
mAdapter = DiffAdapter(this, mDatas)
mRv?.adapter = mAdapter
}
private fun initData() {
mDatas = mutableListOf()
mDatas?.add(MyBean("测试1", "Android", R.drawable.bg_ranking))
mDatas?.add(MyBean("测试2", "Java", R.drawable.ic_pic_add))
mDatas?.add(MyBean("测试3", "C", R.drawable.abc_vector_test))
mDatas?.add(MyBean("测试4", "PHP", R.drawable.bg_daily_btn))
mDatas?.add(MyBean("测试5", "Python", R.drawable.bg_live_top))
}
/**
* 模拟刷新操作
*
* @param view
*/
fun onRefresh(view: View?) {
try {
val oldData = mutableListOf()
mDatas?.let { oldData.addAll(it) }
mDatas?.add(MyBean("李小龙", "帅", R.drawable.bg_live_bottom))
val myBean = mDatas?.removeAt(1)
if (myBean != null) {
mDatas?.add(myBean)
}
// 之前的方案
// mAdapter?.notifyDataSetChanged()
// 新方案
val diffResult = DiffUtil.calculateDiff(DiffUtilCallBack(oldData, mDatas), true)
diffResult.dispatchUpdatesTo(mAdapter!!)
} catch (e: CloneNotSupportedException) {
e.printStackTrace()
}
}
}
demo分析: 使用新方案,当有数据需要刷新时,刷新数据的效率更高,并且数据的刷新过程可以看到有意思的动画,有兴趣可以实操感受下。