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

自定义RecyclerView的DefaultItemAnimator删除未完成动画item出现残留问题

问题根源
    1. 未正确实现endAnimation/endAnimations方法
      你重写的这两个方法只调用了父类逻辑,没有处理自己维护的animators集合里的自定义动画。RecyclerView在item移除、列表清空、视图销毁时都会主动调用这两个方法终止未完成动画,你的自定义动画完全没有被纳入终止逻辑,加上你设置了动画无限循环,只要不主动cancel就会一直运行,即使item已经被移除。
    1. 全局animator成员变量设计错误
      你定义了类级别的animator变量,每次触发新动画都会覆盖这个变量的值,非ANIMATE分支的animator.cancel()只会取消最近一次创建的动画,其他历史运行的动画完全不会被终止,产生大量泄漏的运行中动画。
    1. 未处理item移除场景的动画终止
      你没有重写animateRemove方法,当item被删除时,对应ViewHolder的运行中动画不会被主动取消,也不会从animators集合中移除,是动画残留的直接原因。
    1. 延迟任务无有效性校验
      你用postDelayed触发notifyItemChanged,当item在延迟等待期间被删除,延迟任务执行时会对不存在的item/已复用的ViewHolder触发动画,进一步加剧错乱。
修复方案
class CustomAnimator: DefaultItemAnimator() {
    companion object {
        const val PAYLOAD = "PAYLOAD"
        private val ANIMATE = ItemHolderInfo()
    }

    private val animators = ConcurrentHashMap<RecyclerView.ViewHolder, Animator>()

    override fun canReuseUpdatedViewHolder(viewHolder: RecyclerView.ViewHolder, payloads: MutableList<Any>): Boolean {
        return true
    }

    override fun recordPreLayoutInformation(state: RecyclerView.State, viewHolder: RecyclerView.ViewHolder, changeFlags: Int, payloads: MutableList<Any>): ItemHolderInfo {
        if (PAYLOAD in payloads)
            return ANIMATE
        return super.recordPreLayoutInformation(state, viewHolder, changeFlags, payloads)
    }

    override fun animateChange(oldHolder: RecyclerView.ViewHolder, newHolder: RecyclerView.ViewHolder, preInfo: ItemHolderInfo, postInfo: ItemHolderInfo): Boolean {
        if (preInfo === ANIMATE) {
            // 用局部变量替代全局变量避免覆盖问题
            val animator = ObjectAnimator
                .ofFloat(newHolder.itemView, "alpha", 1f, 0f, 1f)
                .setDuration(5000L)
            animator.repeatCount = Animation.INFINITE
            animator.doOnEnd {
                newHolder.itemView.alpha = 1f
                animators.remove(newHolder)
                dispatchAnimationFinished(newHolder)
                if (oldHolder != newHolder) dispatchAnimationFinished(oldHolder)
            }
            // 启动新动画前先终止当前ViewHolder可能存在的旧动画
            endAnimation(newHolder)
            animators[newHolder] = animator
            animator.start()
            return false
        }
        return super.animateChange(oldHolder, newHolder, preInfo, postInfo)
    }

    override fun animateRemove(holder: RecyclerView.ViewHolder): Boolean {
        // item移除时先终止对应动画
        endAnimation(holder)
        return super.animateRemove(holder)
    }

    override fun endAnimation(item: RecyclerView.ViewHolder) {
        super.endAnimation(item)
        // 终止当前ViewHolder的自定义动画
        animators.remove(item)?.let { animator ->
            if (animator.isRunning) {
                animator.cancel()
            }
        }
        item.itemView.alpha = 1f
    }

    override fun endAnimations() {
        super.endAnimations()
        // 终止所有自定义动画
        animators.values.forEach { animator ->
            if (animator.isRunning) {
                animator.cancel()
            }
        }
        animators.clear()
    }
}

同时建议给延迟触发动画的逻辑加有效性校验,避免对已删除的item触发动画:

recyclerview.postDelayed({
    // 执行前校验index是否在适配器当前数据范围内
    if (index in 0 until adapter.itemCount) {
        adapter.notifyItemChanged(index, CustomAnimator.PAYLOAD)
    }
}, 200)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 07:54:03