首页 > 解决方案 > RecyclerView 最初出现时不会为项目设置动画,只有当它再次被回收时

问题描述

我在 aTranslateAnimation的一个项目中有一个视图RecyclerView.Adapter。动画应在最初出现时应用于特定的列表项,但仅在您向后滚动并再次回收该项目时才有效。

我的猜测是它与 RecyclerView 的生命周期有关,但我无法弄清楚是什么导致动画无法启动。

class mAdapter(items: List<String>): RecyclerView.Adapter<mAdapter.ViewHolder>(){

  private var mPosition = 0

  // The animation will be applied to the first item
  override fun getItemViewType(position: Int): Int {
    if (position == mPosition){
      return 1
    } else {
      return 0
    }
  }

  override fun onViewRecycled(holder: ViewHolder) {
        super.onViewRecycled(holder)

        // Animate the view inside the item
        if (holder.itemViewType == 1){
            holder.animateItem()
        }
    }

  override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
        super.onBindViewHolder(holder, position, payloads)

        // Animate the view inside the item
        if (holder.itemViewType == 1){
          holder.animateItem()
        }
    }

  inner class ViewHolder(view: View): RecyclerView.ViewHolder(view){
    val picture: ImageView? = view.findViewById(R.id.picture)       

    fun animateItem(){
      val itemWidth: Float = itemView.width.toFloat()
      val animator = TranslateAnimation(-itemWidth, 0f, 0f, 0f)

      animator.repeatCount = 0
      animator.interpolator = AccelerateInterpolator(1.0f)
      animator.duration = 700
      animator.fillAfter = true

      background?.animation = animator
      background?.startAnimation(animator)
    }  

  }

}

如果我在其中记录一条消息animateItem,它将在 RecycleView 加载时出现,但在我向下和向上滚动之前它不会动画。

回答

正如NSimon指出的那样,解决方案是编写一个addOnGlobalLayoutListener

override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
  holder.itemView.viewTreeObserver.addOnGlobalLayoutListener{
    holder.animateItem()
  }
}

标签: androidlistviewkotlinandroid-animation

解决方案


正如评论中所讨论的,您的第一个动画实际上正在正常播放。但是,第一次调用时,视图并没有完全分层显示在屏幕上。因此,itemView.width.toFloat()返回 0,并且您正在从 0 动画到 0。

快速的解决方案是将动画的启动封装在 GlobalLayoutListener 中(这是系统告诉您视图已分层)。

像这样的东西:

override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
  holder.itemView.viewTreeObserver.addOnGlobalLayoutListener{
    holder.animateItem()
  }
}

但是请记住,一旦开始动画,您应该删除 globalLayoutListener (否则它会永远留在那里,并且如果/当视图发生某些事情时会继续触发)。所以更好的方法是创建一个像这样的辅助函数:

inline fun <T: View> T.afterMeasured(crossinline f: T.() -> Unit) {
        viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {    
        override fun onGlobalLayout() {    
            if (measuredWidth > 0 && measuredHeight > 0) {    
                viewTreeObserver.removeOnGlobalLayoutListener(this)    
                f()    
            }    
        }    
    })   
}

并像这样在 onBindViewHolder 内部调用它:

override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
  holder.itemView.afterMeasured{
    holder.animateItem()
  }
}

推荐阅读