如何修复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
相关产品推荐
相关产品推荐

