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

RecyclerView删除动画中getOldPosition()始终返回-1如何解决

问题原因

ViewHolder.getOldPosition() 仅在RecyclerView预布局(pre-layout)阶段返回有效值,当执行到runPendingAnimations时,布局更新流程已经完成,系统会自动将oldPosition重置为-1,所以这个阶段读取必然拿到无效值。而animateRemove回调恰好是在预布局阶段触发的,这时候读取的oldPosition是正确的。


解决方案

在animateRemove触发时就把对应的旧位置存下来,和ViewHolder绑定存储,不要等到runPendingAnimations再去读取。修改后参考代码如下:

class MyItemAnimator: SimpleItemAnimator() {
  // 存储ViewHolder和对应的旧位置,不要单独存ViewHolder
  private val pendingRemovals = ArrayList<Pair<RecyclerView.ViewHolder, Int>>()

  override fun animateRemove(holder: RecyclerView.ViewHolder): Boolean {
    // 预布局阶段直接读取oldPosition存起来
    val oldPos = holder.oldPosition
    pendingRemovals.add(Pair(holder, oldPos))
    // 返回true表示自定义执行动画,触发后续runPendingAnimations流程
    return true
  }
  
  override fun runPendingAnimations(){
    pendingRemovals.forEach { (holder, oldPos) ->
      holder.itemView.animate().apply {
        translationX(holder.itemView.rootView.width.toFloat())
        alpha(0F)
        // 用提前存好的oldPos计算延时
        startDelay = oldPos * 50L
        duration = removeDuration
        setListener(object : Animator.AnimatorListener {
          override fun onAnimationEnd(animation: Animator?) {
            // 动画结束必须调用dispatchRemoveFinished通知RecyclerView回收资源
            dispatchRemoveFinished(holder)
            pendingRemovals.remove(Pair(holder, oldPos))
            if (pendingRemovals.isEmpty()) {
              dispatchAnimationsFinished()
            }
          }
          override fun onAnimationStart(animation: Animator?) {}
          override fun onAnimationCancel(animation: Animator?) {}
          override fun onAnimationRepeat(animation: Animator?) {}
        })
      }.start()
    }
  }

  // 剩余必须实现的基础方法按默认逻辑实现即可
  override fun isRunning(): Boolean {
    return pendingRemovals.isNotEmpty()
  }

  override fun animateAdd(holder: RecyclerView.ViewHolder): Boolean {
    dispatchAddFinished(holder)
    return false
  }

  override fun animateMove(holder: RecyclerView.ViewHolder, fromX: Int, fromY: Int, toX: Int, toY: Int): Boolean {
    dispatchMoveFinished(holder)
    return false
  }

  override fun animateChange(oldHolder: RecyclerView.ViewHolder?, newHolder: RecyclerView.ViewHolder?, fromX: Int, fromY: Int, toX: Int, toY: Int): Boolean {
    if (oldHolder != newHolder && oldHolder != null) dispatchChangeFinished(oldHolder, true)
    if (newHolder != null) dispatchChangeFinished(newHolder, false)
    return false
  }

  override fun endAnimation(item: RecyclerView.ViewHolder) {
    item.itemView.animate().cancel()
  }

  override fun endAnimations() {
    pendingRemovals.forEach { it.first.itemView.animate().cancel() }
    pendingRemovals.clear()
  }
}

额外注意事项

  • 自定义ItemAnimator时,动画结束后必须调用对应的dispatchXXXFinished方法通知RecyclerView回收资源,否则会出现内存泄漏、条目渲染异常等问题
  • 如果你需要保留系统默认的增、改、移动动画,直接继承DefaultItemAnimator而非SimpleItemAnimator会更省事,仅重写animateRemove和runPendingAnimations两个方法即可。

内容的提问来源于stack exchange,提问作者Some Noob Student

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 04:36:04