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

如何等待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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 11:35:40