美文网首页
用DiffUtil刷新RecyclerView的一个实例

用DiffUtil刷新RecyclerView的一个实例

作者: 超级绿茶 | 来源:发表于2019-12-28 19:28 被阅读0次

从上古时期就开始做Android开发的同学都有一个爱好;喜欢在刷新列表时直接用notifyDataSetChanged,要问为啥?因为在RecyclerView之前只有ListView,这玩意的适配器刷新只有这一个方法,所以很自然的把这种习惯延续到了RecyclerView上面。但到了RecyclerView时情况有了变化,为了提高刷新效率RecyclerView的Adapter出现了各种以notiftyItem开头的刷新方法,以适应不同的应用场合,而且谷歌为了怕你们这帮开发人员不用,还免费的附送动画效果。

notifyItemChanged(int) 
notifyItemInserted(int)
notifyItemRemoved(int)
notifyItemRangeChanged(int, int)
notifyItemRangeInserted(int, int)
notifyItemRangeRemoved(int, int)

这种以notifyItem开头的方法被称为局部刷新,比notifyDataSetChanged的那种全局刷新更有效率。而且本着送佛送到西,好人做到底的态度,谷歌在Android 7.0的时候又出一个名为DiffUtil的工具类,这个工具类的作用是比较两个集合的不同处,然后在内部会根据比较结果来选择合适的局部刷新方法,省去我们自动调用的麻烦,所以更方便了。

但这里有一个问题:为什么工具类需要传入两个什么样的集合?

两个集合分别是列表现在显示的数据集——旧集合,和将要显示的数据集——新集合。

例如我们在开发时经常会遇到这样的情况:当前的列表有二十条数据集,当我们把RecyclerView拉到底部时会触发加载更多的操作,网络接口会返回十条数据集以便于我们追加到原来集合后面。所以,旧集合里保存的是当前列表的二十条记录,新集合是被追加了十条后变成三十条记录的集合。不要以为是旧集合是二十条,新集合是追加的十条。

说完概念后来说一些实际的用法。首先要自定义一个继承了DiffUitl.Callback的类,这是一个抽象类,我们需要重写四个方法,这四个方法在后续的步骤中会被调用以确定需要刷新的部分。

  • getOldListSize():返回旧数据集合的长度。
  • getNewListSize():返回新数据集合的长度。如果新旧长度不同就说明不是增就是删。
  • areItemsTheSame(int oldItemPosition, int newItemPosition):判断集合中指定位置的两个对象是否是同一个对象。一般对象总会有一个ID值或能标识唯一性的值,通过比较这个值是否相同我们能判定相同位置的两个对象是否是同一个对象。例如:可以通过学生ID号来判断两个对象是否为同一个对象。相同为true,不同为false。
  • areContentsTheSame(int oldItemPosition, int newItemPosition):光是通过ID号的比较只能判定两个对象是否为同一个对象,但不能判定两个对象的内容是否相同。例如学生ID可以不变,但学生的年纪或姓名是可以不同的,所以这方法的返回值为false是表示学生信息被修改了,需要做修改刷新。

DiffUtil类最后就是通过这个自定义的类来判定刷新时采用哪个notifyItemXXX
方法。DiffUtil的调用也很简单:

val result = DiffUtil.calculateDiff(DiffUtil.Callback(oldList,newList))
resultDiffUtil.dispatchUpdatesTo(adapter)

最后说明一下calculateDiff方法执行起来可能非常耗时,所以最好在子线程运行。而dispatchUpdatesTo需要在主线程运行。

为了便于理解,我这里做了一个简易的DEMO


DEMO效果

实体类:

data class UserBean(val name: String, val age: Int = 20, var isSelect: Boolean = false)

MainActivity.kt


import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.list_item.view.*
import kotlinx.coroutines.*

class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
    private lateinit var userViewModel: UserViewModel
    private lateinit var adapter: MainAdapter
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        initViewModel()
        initView()
    }

    override fun onDestroy() {
        super.onDestroy()
        cancel()
    }

    private fun initViewModel() {
        userViewModel = ViewModelProviders.of(this).get(UserViewModel::class.java)
        // 订阅集合更新事件
        userViewModel.listLiveData.observe(this, Observer { updateRecyclerView(it) })
        userViewModel.onRefresh() // 生成模拟数据
    }

    private fun initView() {
        initRecyclerView()
        btnAdd.setOnClickListener { userViewModel.onRefresh(true) }
        btnDelete.setOnClickListener { userViewModel.deleteUserBySelect() }
        btnInsert.setOnClickListener { userViewModel.insertTop() }
    }

    private fun initRecyclerView() {
        rvMain.layoutManager = LinearLayoutManager(this)
        this.adapter = MainAdapter(mutableListOf(), this)
        rvMain.adapter = this.adapter
    }

    /**
     * 刷新列表,本DEMO的核心部分
     * 这个方法用到协程进行线程调度,因为集合比较操作可能会非常耗时
     */
    private fun updateRecyclerView(listNewData: MutableList<UserBean>) = launch {
        // 1)从adapter内取出现有的集合
        val listOldData = adapter.getData()
        // 2)比较新旧两个集合的不同处并返回结果
        val result = withContext(Dispatchers.IO) {
            DiffUtil.calculateDiff(object : DiffUtil.Callback() {
                override fun getOldListSize(): Int {
                    return listOldData.size
                }

                override fun getNewListSize(): Int {
                    return listNewData.size
                }

                /**
                 * 判断集合的指定位置的两个对象是否同一个对象。
                 * 一般从接口拿的记录都会带一个ID值,或者自定义一个ID值作为唯一标识。
                 * 如果这个值相同的话则返回true,否则返回false
                 */
                override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
                    return listOldData[oldItemPosition].name == listNewData[newItemPosition].name
                }

                /**
                 * 当areItemsTheSame返回true时再判断对象的内容是否相同
                 * 如果返回false就表示这个对象是属于修改操作
                 */
                override fun areContentsTheSame(
                    oldItemPosition: Int,
                    newItemPosition: Int
                ): Boolean {
                    val oldUser = listOldData[oldItemPosition]
                    val newUser = listNewData[newItemPosition]
                    return oldUser.age == newUser.age && oldUser.isSelect == newUser.isSelect
                }
            }, true)
        }
        // 3)比较完成再在主线程上刷新adapter,dispatchUpdateTo会根据的结果在内部调用不同的notifyItemXXXX方法
        result.dispatchUpdatesTo(adapter)
        // 4)将集合保存到adapter以供下次比较时使用
        adapter.setData(listNewData)
    }
}

/**
 * 自定义列表的适配器
 */
class MainAdapter(
    private val listData: MutableList<UserBean> = mutableListOf(),
    private val activity: MainActivity
) : RecyclerView.Adapter<MainAdapter.UserViewHolder>() {

    /**
     * 设置列表器的集合
     */
    fun setData(listNewData: MutableList<UserBean>) {
        this.listData.clear()
        this.listData += listNewData
    }

    fun getData() = listData

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false)
        return UserViewHolder(view)
    }

    override fun getItemCount(): Int {
        return listData.size
    }

    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        holder.itemView.apply {
            tvName.text = listData[position].name
            tvAge.text = listData[position].age.toString()
            chkSelect.isChecked = listData[position].isSelect
            chkSelect.setOnClickListener {
                val vm = ViewModelProviders.of(activity).get(UserViewModel::class.java)
                vm.selectUser(holder.adapterPosition, chkSelect.isChecked)
            }
            btnDelete.setOnClickListener {
                val vm = ViewModelProviders.of(activity).get(UserViewModel::class.java)
                vm.deleteUser(holder.adapterPosition)
            }
        }
    }

    inner class UserViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
}

UserViewModel.kt


import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import kotlin.random.Random

class UserViewModel : ViewModel() {
    val listLiveData = MutableLiveData<MutableList<UserBean>>()

    /**
     * 设置指定位置的记录为勾选
     */
    fun selectUser(position: Int, isSelect: Boolean) {
        val listUser: MutableList<UserBean> = listLiveData.value ?: mutableListOf()
        listUser[position].isSelect = isSelect
        listLiveData.postValue(listUser)
    }

    /**
     * 删除单条记录
     */
    fun deleteUser(position: Int) {
        val listUser: MutableList<UserBean> = listLiveData.value ?: mutableListOf()
        listUser.removeAt(position)
        listLiveData.postValue(listUser)
    }

    /**
     * 删除勾选项
     */
    fun deleteUserBySelect() {
        val listUser: MutableList<UserBean> = listLiveData.value ?: mutableListOf()
        listUser.removeAll { it.isSelect }
        listLiveData.postValue(listUser)
    }

    /**
     * 在顶部插入一条记录
     */
    fun insertTop() {
        val listUser: MutableList<UserBean> = listLiveData.value ?: mutableListOf()
        val age = Random(System.currentTimeMillis()).nextInt(20, 40)
        val bean = UserBean(System.currentTimeMillis().toString(), age)
        listUser.add(1, bean)
        listLiveData.postValue(listUser)
    }

    /**
     * 刷新记录
     * @param isAdd true表示追加
     */
    fun onRefresh(isAdd: Boolean = false) {
        val listUser: MutableList<UserBean> = listLiveData.value ?: mutableListOf()
        if (!isAdd) listUser.clear()
        listUser += addTenUser()
        listLiveData.postValue(listUser)
    }

    /**
     * 创建10条记录
     */
    private fun addTenUser(): List<UserBean> {
        val list = mutableListOf<UserBean>()
        repeat(10) {
            val age = Random(System.currentTimeMillis()).nextInt(20, 40)
            val bean = UserBean(System.currentTimeMillis().toString(), age)
            list += bean
            Thread.sleep(10)
        }
        return list
    }

}

源码下载

点击链接加入QQ群聊:https://jq.qq.com/?_wv=1027&k=5z4fzdT
或关注微信公众号:口袋里的安卓

相关文章

网友评论

      本文标题:用DiffUtil刷新RecyclerView的一个实例

      本文链接:https://www.haomeiwen.com/subject/gugsoctx.html