自定义RecyclerView的DefaultItemAnimator删除未完成动画item出现残留问题
问题根源
- 未正确实现
endAnimation/endAnimations方法
你重写的这两个方法只调用了父类逻辑,没有处理自己维护的animators集合里的自定义动画。RecyclerView在item移除、列表清空、视图销毁时都会主动调用这两个方法终止未完成动画,你的自定义动画完全没有被纳入终止逻辑,加上你设置了动画无限循环,只要不主动cancel就会一直运行,即使item已经被移除。
- 未正确实现
- 全局
animator成员变量设计错误
你定义了类级别的animator变量,每次触发新动画都会覆盖这个变量的值,非ANIMATE分支的animator.cancel()只会取消最近一次创建的动画,其他历史运行的动画完全不会被终止,产生大量泄漏的运行中动画。
- 全局
- 未处理item移除场景的动画终止
你没有重写animateRemove方法,当item被删除时,对应ViewHolder的运行中动画不会被主动取消,也不会从animators集合中移除,是动画残留的直接原因。
- 未处理item移除场景的动画终止
- 延迟任务无有效性校验
你用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
相关产品推荐
相关产品推荐

