如何等待RecyclerView动画完成后再调整其尺寸?
我在一个RelativeLayout中嵌套了RecyclerView,这个RelativeLayout通过约束垂直固定在两个按钮之间:
<RelativeLayout android:id="@+id/recvPlayersLayout" android:layout_width="300dp" android:layout_height="0dp" android:layout_marginTop="16dp" android:layout_marginBottom="8dp" app:layout_constraintBottom_toTopOf="@+id/btnAddPlayer" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/btnPremium"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recvPlayers" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerVertical="true" tools:listitem="@layout/recv_players_row" /> </RelativeLayout>
这个RelativeLayout会占满两个按钮之间的全部空间,内部的RecyclerView是垂直居中显示的。
在Activity中,我给RecyclerView做了如下配置:
playerRV.itemAnimator = DefaultItemAnimator() playerRV.layoutManager = LinearLayoutManager(this)
通过notifyItemInserted()和notifyItemRemoved()来添加、删除列表条目。
现在遇到的问题是:添加/删除条目时,RecyclerView的高度会在动画播放前就调整。添加条目时动画显示正常,但删除条目时完全看不到动画效果。
我发现如果先给RecyclerView设置固定高度,就能看到删除动画:
val layoutParams1 = binding.recvPlayers.layoutParams layoutParams1.height = binding.recvPlayers.height binding.recvPlayers.layoutParams = layoutParams1 (playerRV.adapter as PlayerRecyclerViewAdapter).notifyItemRemoved(hintList.size)
但之后需要把高度恢复为wrap_content:
val layoutParams2 = binding.recvPlayers.layoutParams layoutParams2.height = ViewGroup.LayoutParams.WRAP_CONTENT binding.recvPlayers.layoutParams = layoutParams2
请问有没有办法等动画完全结束后再执行高度恢复的操作?或者有没有更合适的实现方式?
我试过用runBlocking,但动画还是看不到,不确定用法是否正确:
val layoutParams1 = binding.recvPlayers.layoutParams layoutParams1.height = binding.recvPlayers.height binding.recvPlayers.layoutParams = layoutParams1 runBlocking { val job = launch { (playerRV.adapter as PlayerRecyclerViewAdapter).notifyItemRemoved(hintList.size) } job.join() val layoutParams2 = binding.recvPlayers.layoutParams layoutParams2.height = ViewGroup.LayoutParams.WRAP_CONTENT binding.recvPlayers.layoutParams = layoutParams2 }
方法一:监听RecyclerView动画结束事件
RecyclerView的ItemAnimator提供了动画监听接口,可以在所有动画结束后再恢复高度,避免提前改变布局打断动画:
// 先设置固定高度锁定布局 val layoutParams = binding.recvPlayers.layoutParams layoutParams.height = binding.recvPlayers.height binding.recvPlayers.layoutParams = layoutParams // 获取默认动画器并设置监听 val animator = playerRV.itemAnimator as DefaultItemAnimator animator.setListener(object : DefaultItemAnimator.AnimatorListener() { override fun onAnimationFinished(animator: RecyclerView.ItemAnimator?) { super.onAnimationFinished(animator) // 动画结束后恢复wrap_content val params = binding.recvPlayers.layoutParams params.height = ViewGroup.LayoutParams.WRAP_CONTENT binding.recvPlayers.layoutParams = params // 移除监听防止重复触发 animator?.setListener(null) } }) // 触发删除动画 (playerRV.adapter as PlayerRecyclerViewAdapter).notifyItemRemoved(hintList.size)
方法二:简化布局结构,避免高度提前变化
问题根源是外层RelativeLayout的0dp高度配合RecyclerView的wrap_content+layout_centerVertical,导致删除条目时父布局高度瞬间变化覆盖动画。可以直接让RecyclerView使用约束布局,去掉外层RelativeLayout:
<androidx.recyclerview.widget.RecyclerView android:id="@+id/recvPlayers" android:layout_width="300dp" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:layout_marginBottom="8dp" app:layout_constraintBottom_toTopOf="@+id/btnAddPlayer" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/btnPremium" app:layout_constraintVertical_bias="0.5" <!-- 实现垂直居中效果 --> tools:listitem="@layout/recv_players_row" />
这样RecyclerView直接通过约束固定在两个按钮之间,删除条目时高度变化会配合动画自然过渡,不需要手动设置固定高度。
方法三:自定义LayoutManager,动画期间锁定高度
如果必须保留原有布局结构,可以自定义LinearLayoutManager,在动画执行期间强制保持当前高度:
class FixedHeightLinearLayoutManager(context: Context) : LinearLayoutManager(context) { private var isAnimating = false override fun onMeasure(recycler: RecyclerView.Recycler, state: RecyclerView.State, widthSpec: Int, heightSpec: Int) { if (isAnimating) { // 动画期间使用当前高度,不重新测量 val currentHeight = recyclerView?.height ?: 0 super.onMeasure(recycler, state, widthSpec, View.MeasureSpec.makeMeasureSpec(currentHeight, View.MeasureSpec.EXACTLY)) } else { super.onMeasure(recycler, state, widthSpec, heightSpec) } } fun setAnimating(animating: Boolean) { isAnimating = animating } }
使用方式:
val layoutManager = FixedHeightLinearLayoutManager(this) playerRV.layoutManager = layoutManager // 删除条目时锁定高度 layoutManager.setAnimating(true) (playerRV.adapter as PlayerRecyclerViewAdapter).notifyItemRemoved(hintList.size) // 监听动画结束,恢复正常测量 val animator = playerRV.itemAnimator as DefaultItemAnimator animator.setListener(object : DefaultItemAnimator.AnimatorListener() { override fun onAnimationFinished(animator: RecyclerView.ItemAnimator?) { super.onAnimationFinished(animator) layoutManager.setAnimating(false) playerRV.requestLayout() animator?.setListener(null) } })
内容的提问来源于stack exchange,提问作者Jabber

