使用RecyclerView动态改变item时遇到的坑

2,461 阅读2分钟

最近想到优化一下自己的应用,用动态改变item代替之前的一股脑notifyDataSetChanged,提升一下速度,而且动态改变还能实现动画效果。

老代码:

private fun listItemChanged(v: View? = null) {
    ((v ?: view)!!.list.adapter as RMAdapter).let {
        it.data = getNewData()
        it.notifyDataSetChanged()
    }
}

新代码:

private fun listItemChanged(v: View? = null) {
    ((v ?: view)!!.list.adapter as RMAdapter).let {
        val newData = getNewData()
        val diffResult = DiffUtil.calculateDiff(DiffCallBack(it.data, newData), true)
        diffResult.dispatchUpdatesTo(it)
        it.data = newData
    }
}

可以看到,这里使用了一个叫DiffUtil的东西,是在官方的support v7包里自带的。功能是检测两个数据集之间的差别,然后通过dispatchUpdatesTo帮你调用Adapter里对应的动态更改item的方法。原理是使用了一个1986年提出的Myers差分算法,让检查差别的速度飞快,官方数据是在安卓6.0的Nexus 5X上检测出1000个项目中的50个改动只要3.5ms。官方页面

具体的使用方式我就不详细说了,毕竟这不是这篇文章的主题。

所以....目前看上去一切都好?

遇到的错位问题

删除没有问题,问题是,删除之后item都错位了。(其实这花了我挺久才意识到这是错位而不是什么其它奇奇怪怪的问题)

立马跑去onBindViewHolder设断点

//简化的代码,只保留了关键部分
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
    holder.itemView.setOnClickListener {
        openChatActivity(data[position].grokid)  //在此处设断点后发现position为1
    }
}

删除第一项后明明position应该为0啊,然后我就懵了,这难道是官方bug?

去网上搜了一些博文,并没有找到什么解决办法,甚至有人是在用完DiffUtil后再用notifyDataSetChanged刷新一遍(我:....)

解决

我最后当然是解决了,否则也没有这篇文章了

睡了一觉起来后我突然注意到onBindViewHolder这个名字,这是在设置布局的时候才会调用的啊!而设置布局的时候这一项的确在第二的位置。而动态改变item并不会像**notifyDataSetChanged**那样全部重新设置一遍布局,所以这里的老的**position**,用到了新的**data**表上,造成了错位。

问题找到就很好解决了:在一开始的时候就把值记录下来,无视之后data表的变化

代码:

override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
    val grokid = data[position].grokid      //加上这一行
    holder.itemView.setOnClickListener {
        openChatActivity(grokid)
    }
}

至此,问题解决,可以美滋滋地享受动画了


第一次写文章,请多多指教!