使用 LiveData、Room 和 DiffUtil 更新 RecyclerView 项目内的内部视图

Update inner view inside RecyclerView item with LiveData, Room and DiffUtil

我在 Room 中有消息 table,结构如下:id、text、animRes、readStatus。

然后我订阅 room 暴露的 livedata 以使用 diffutil 适配器在 recyclerview 中显示此项目。

当 RV 中出现新消息时,动画开始播放。如果我将 readStatus 添加到 diffutil areContentsTheSame 函数,每次 readStatus 更改时我的动画都会重新启动,这就是为什么我不能只使用 diffutil 重绘完整视图的原因。

当房间的价值发生变化时,有什么方法可以只更新 RV 项目内的一个内部指示器视图吗? 这在 Messenger 中如何工作,更改阅读状态不会重新启动动画(贴纸或 gif)?

谢谢)

终于找到答案了)

实施 DiffUtil(getChangePayload 应该 return 一些东西,如果你想部分更新,否则 return null 完全重新绑定):

    class DiffUtilCallback : DiffUtil.ItemCallback<MessageModel>() {
        override fun getChangePayload(oldItem: MessageModel, newItem: MessageModel): Any? {
            if (oldItem.readStatus!=newItem.readStatus) {
                return Bundle().apply {
                    putInt("readStatus", newItem.readStatus)
                }
            }
            return null
        }
        override fun areItemsTheSame(oldItem: MessageModel, newItem: MessageModel): Boolean {
            return oldItem == newItem
        }

        override fun areContentsTheSame(oldItem: MessageModel, other: MessageModel): Boolean {
            oldItem.apply {
                return id == other.id && text == other.text && animRes == other.animRes && readStatus == other.readStatus
            }
        }
    }

然后在适配器中(此处使用 ViewBinding 的示例:):

    class MessageAdapter : ListAdapter<MessageModel, MessageViewHolder>(DiffUtilCallback()) {

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageViewHolder {
                val binding = InboundMessageBinding.inflate(LayoutInflater.from(parent.context), parent, false)
                return MessageViewHolder(binding)
        }

        override fun onBindViewHolder(holder: MessageViewHolder, position: Int) {
            holder.bind(getItem(position))
        }

        override fun onBindViewHolder(holder: MessageViewHolder, position: Int, payloads: MutableList<Any>) {
            if (payloads.isNotEmpty()) {
                val item = payloads[0] as Bundle
                val readStatus = item.getInt("readStatus")
                holder.binding.status.text = readStatus.toString()
                return
            }
            super.onBindViewHolder(holder, position, payloads)
        }
    }

仅当 getChangePayload from diffUtil return 为空时,我们才调用 onBindViewHolder 构造函数。否则只要改变目标视图,简单有效)

更新:如果在添加动画播放时负载发生变化,两个动画相互重叠,效果不佳。

可以这样解决:

    messageRv.itemAnimator = object : DefaultItemAnimator()  {
        override fun animateChange(oldHolder: RecyclerView.ViewHolder?, newHolder: RecyclerView.ViewHolder?, fromX: Int, fromY: Int, toX: Int, toY: Int): Boolean {
            if ((oldHolder as? MessageViewHolder)?.itemId==(newHolder as? MessageViewHolder)?.itemId) { //don't animate same view to prevent blinking when only readStatus changes
                dispatchChangeFinished(newHolder, true) //for correct next animation
                return true
            }
            return super.animateChange(oldHolder, newHolder, fromX, fromY, toX, toY)
        }
    }