You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修复RecyclerView结合Lottie点赞动画滚动时异常播放问题?

RecyclerView点赞动画随机触发问题修复

问题场景

RecyclerView的Item通过Lottie实现点赞动画,快速滚动列表时,无点赞状态的Item会随机播放点赞动画。当前已自定义SampleItemAnimator处理动画逻辑,通过DiffUtil传递状态变更payload,使用FastAdapter实现ViewHolder。

核心原因

快速滚动时ViewHolder复用,ItemAnimator可能持有过时的ItemHolderInfo,或ViewHolder复用前未彻底重置Lottie动画状态,导致残留的动画逻辑被触发。


解决方案

1. 在ViewHolder绑定阶段强制重置动画状态

复用ViewHolder时,先彻底清理Lottie的动画状态,再根据当前Item数据设置状态,避免残留动画影响。

修改SampleItem的bindView方法:

class SampleItem(
    val markableItem: MarkableItem,
    private val onClickItem: (Item) -> Unit
) : AbstractBindingItem<ItemSampleBinding>() {
    // ... 原有代码省略 ...

    override fun bindView(binding: ItemSampleBinding, payloads: List<Any>) {
        super.bindView(binding, payloads)
        with(binding) {
            // 第一步:强制重置动画状态,清除复用残留
            resetLikeAnimation(likeAnimation)
            
            // 后续绑定逻辑
            itemName.text = markableItem.item.name
            itemImage.setContent(markableItem.item, IMAGE_SIZE)
            likeAnimation.progress = if(markableItem.isLiked) 1f else 0f
            root.setThrottleClickListener { onClickItem(markableItem.item) }
        }
    }

    private fun resetLikeAnimation(lottie: LottieAnimationView) {
        lottie.cancelAnimation()
        lottie.progress = 0f
        lottie.removeAllAnimatorListeners()
    }
}

2. 修正ItemAnimator的动画触发逻辑

在animateChange中,结合当前ViewHolder绑定的实际数据校验状态,避免依赖过时的ItemHolderInfo触发错误动画。

修改SampleItemAnimator的animateChange方法:

class SampleItemAnimator : DefaultItemAnimator() {
    // ... 原有代码省略 ...

    override fun animateChange(
        oldHolder: RecyclerView.ViewHolder,
        newHolder: RecyclerView.ViewHolder,
        preInfo: ItemHolderInfo,
        postInfo: ItemHolderInfo
    ): Boolean {
        val holder = newHolder as BindingViewHolder<ItemSampleBinding>
        val lottie = holder.binding.sampleAnimation
        
        // 先重置动画,避免残留状态干扰
        resetAnimation(lottie)

        val animator = lottieAnimatorListener {
            dispatchAnimationFinished(holder)
            lottie.removeAllAnimatorListeners()
        }
        lottie.addAnimatorListener(animator)

        if (preInfo is SampleItemHolderInfo) {
            // 结合当前Item的实际状态校验,避免使用过时信息
            val currentMarkableItem = (holder.itemView.tag as? SampleItem)?.markableItem
            if (preInfo.isItemLicked && currentMarkableItem?.isLiked == true) {
                lottie.playAnimation()
            } else {
                resetAnimation(lottie)
            }
            return true
        }

        return super.animateChange(oldHolder, newHolder, preInfo, postInfo)
    }
}

3. 优化DiffUtil的内容判断逻辑

确保areContentsTheSame仅在内容真正一致时返回true,避免不必要的动画触发:

class SampleListDiffCallback : DiffCallback<SampleItem> {
    override fun areContentsTheSame(oldItem: SampleItem, newItem: SampleItem) =
        oldItem.markableItem.isLiked == newItem.markableItem.isLiked &&
        oldItem.markableItem.item == newItem.markableItem.item

    // ... 原有代码省略 ...
}

4. 强制处理动画结束事件

重写endAnimation方法,当RecyclerView主动结束动画时,强制重置Lottie状态:

class SampleItemAnimator : DefaultItemAnimator() {
    // ... 原有代码省略 ...

    override fun endAnimation(viewHolder: RecyclerView.ViewHolder) {
        super.endAnimation(viewHolder)
        if (viewHolder is BindingViewHolder<ItemSampleBinding>) {
            val lottie = viewHolder.binding.sampleAnimation
            resetAnimation(lottie)
            dispatchAnimationFinished(viewHolder)
        }
    }
}

内容的提问来源于stack exchange,提问作者Maxim Smolyakov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 00:35:32