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

使用Transition API时RecyclerView项为何立即消失?能否实现ScrollView效果?

当然可以用RecyclerView实现和ScrollView完全一致的平滑高度过渡效果!

咱们先拆解下问题根源,再一步步给出可落地的解决方案:

问题本质

RecyclerView的LayoutManager会实时根据当前布局高度计算可见列表项,当你修改LayoutParams触发过渡动画时,它会立刻调整可见范围,把超出新高度的item直接回收/隐藏,完全不等动画跑完。而ScrollView嵌套LinearLayout的结构是把所有子View都保存在内存里,只是通过ScrollView的裁剪逻辑控制显示,所以动画过程中所有item都还在,视觉上自然平滑。


解决方案:自定义过渡控制逻辑

1. 自定义RecyclerView,阻止动画期间的布局更新

先写一个自定义RecyclerView,通过标志位控制是否跳过布局请求,避免LayoutManager提前修改可见item:

class TransitionSafeRecyclerView(context: Context, attrs: AttributeSet?) : RecyclerView(context, attrs) {
    var isAnimatingHeight = false

    override fun requestLayout() {
        // 动画期间跳过布局请求,保持当前可见item状态
        if (!isAnimatingHeight) {
            super.requestLayout()
        }
    }
}

2. 用Transition API监听动画生命周期,切换控制状态

在触发高度变化的代码里,配合ChangeBounds过渡动画,监听动画的开始/结束/取消事件,同步更新自定义RecyclerView的控制标志:

val recyclerView = findViewById<TransitionSafeRecyclerView>(R.id.recycler)
val isExpanded = // 你的展开状态判断逻辑
val targetHeight = if (isExpanded) ViewGroup.LayoutParams.WRAP_CONTENT else 200 // 折叠后的固定高度

// 创建过渡动画,可自定义时长
val heightTransition = ChangeBounds().apply {
    duration = 300
    addListener(object : Transition.TransitionListener {
        override fun onTransitionStart(transition: Transition) {
            recyclerView.isAnimatingHeight = true
            // 强制RecyclerView用当前高度完成测量,锁定可见item
            recyclerView.layoutManager?.onMeasure(
                Recycler(), State(),
                recyclerView.measuredWidth, recyclerView.measuredHeight
            )
            // 可选:临时禁用ItemAnimator,避免item入场/退场动画干扰
            recyclerView.itemAnimator = null
        }

        override fun onTransitionEnd(transition: Transition) {
            recyclerView.isAnimatingHeight = false
            // 动画结束后恢复正常布局和ItemAnimator
            recyclerView.itemAnimator = DefaultItemAnimator()
            recyclerView.requestLayout()
        }

        override fun onTransitionCancel(transition: Transition) {
            // 动画取消时也要恢复状态
            recyclerView.isAnimatingHeight = false
            recyclerView.itemAnimator = DefaultItemAnimator()
            recyclerView.requestLayout()
        }

        // 以下两个方法留空即可
        override fun onTransitionPause(transition: Transition) {}
        override fun onTransitionResume(transition: Transition) {}
    })
}

// 触发过渡动画
TransitionManager.beginDelayedTransition(recyclerView.parent as ViewGroup, heightTransition)
val params = recyclerView.layoutParams
params.height = targetHeight
recyclerView.layoutParams = params

3. 可选:优化折叠时的item显示

如果希望折叠时最后一个可见item能完整显示(而不是被裁剪),可以在动画开始前,先让RecyclerView滚动到顶部/指定位置,再触发高度变化。


方案原理

  • 通过isAnimatingHeight标志位,在动画期间阻止RecyclerView重新计算可见item,让它保持当前的item显示状态。
  • 动画过程中只通过View的高度渐变实现视觉上的折叠/展开,和ScrollView的裁剪逻辑效果一致。
  • 动画结束后立即恢复RecyclerView的正常布局机制,保证它的性能优势(item回收复用)。

内容的提问来源于stack exchange,提问作者Sira Lam

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 03:54:03