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

Jetpack Compose+WindowManager:已分离视图启动动画器崩溃问题

解决悬浮Composable视图在动画未结束时移除导致的崩溃问题

我开发了一款名为Floating-Bubble-View的Android库,用于创建屏幕悬浮视图。近期为其添加了Composable支持,借助自定义LifecycleOwner可正常显示/移除Composable内容,但遇到一个异常场景:当滚动LazyColumn时,若在组件的涟漪动画结束前调用移除视图的方法,应用会崩溃,错误日志如下:

Process: com.torrydo.testfloatingbubble, PID: 29264
java.lang.IllegalStateException: Cannot start this animator on a detached view!
at android.graphics.RenderNode.addAnimator(RenderNode.java:1469)
at android.graphics.animation.RenderNodeAnimator.setTarget(RenderNodeAnimator.java:288)
at android.graphics.animation.RenderNodeAnimator.setTarget(RenderNodeAnimator.java:277)
at android.graphics.drawable.RippleForeground.startPending(RippleForeground.java:139)
at android.graphics.drawable.RippleForeground.drawHardware(RippleForeground.java:168)
at android.graphics.drawable.RippleForeground.draw(RippleForeground.java:340)
at android.graphics.drawable.RippleDrawable.drawBackgroundAndRipples(RippleDrawable.java:848)
at android.graphics.drawable.RippleDrawable.draw(RippleDrawable.java:679)
at android.view.View.drawBackground(View.java:22581)
at android.view.View.draw(View.java:22342)
at androidx.compose.material.ripple.AndroidRippleIndicationInstance.drawIndication(Ripple.android.kt:204)

当前移除视图的函数代码如下:

fun remove() {
     builder.composeView?.also { composeView ->
        builder.composeLifecycleOwner?.onPause()
        builder.composeLifecycleOwner?.onStop()
        windowManager!!.removeView(composeView)
     }
}

布局包含返回按钮与LazyColumn的基础Composable:
布局截图

崩溃原因

崩溃的核心原因是:视图被移除后,Composable组件内的涟漪动画仍在后台执行,此时View已脱离Window,动画无法找到有效的RenderNode目标,从而抛出IllegalStateException。

解决方案

方案1:移除前主动终止所有动画与Drawable回调

在调用windowManager.removeView之前,主动清除ComposeView上的所有动画及Drawable的回调,确保视图脱离后没有残留动画继续执行:

fun remove() {
    builder.composeView?.also { composeView ->
        // 终止视图上的所有动画
        composeView.clearAnimation()
        
        // 清除背景Drawable的动画与回调,避免涟漪继续执行
        composeView.background?.run {
            callback = null
            unscheduleDraw()
        }

        // 处理Lifecycle
        builder.composeLifecycleOwner?.onPause()
        builder.composeLifecycleOwner?.onStop()
        
        // 安全移除视图,捕获重复移除的异常
        try {
            windowManager?.removeView(composeView)
        } catch (e: IllegalArgumentException) {
            e.printStackTrace()
        }
    }
}

方案2:延迟移除等待动画完成

如果无法精准判断动画结束时机,可以延迟一小段时间再移除视图,给涟漪动画足够的完成时间(时间可根据实际效果调整):

fun remove() {
    builder.composeView?.also { composeView ->
        builder.composeLifecycleOwner?.onPause()
        builder.composeLifecycleOwner?.onStop()
        
        // 延迟100ms等待涟漪动画结束
        Handler(Looper.getMainLooper()).postDelayed({
            try {
                windowManager?.removeView(composeView)
            } catch (e: IllegalArgumentException) {
                e.printStackTrace()
            }
        }, 100)
    }
}

方案3:自定义无涟漪的Indication

如果问题仅由Compose Material的涟漪效果导致,可以直接禁用悬浮视图内的涟漪动画,自定义一个无涟漪的Indication:

object NoRippleIndication : Indication {
    private object NoRippleIndicationInstance : IndicationInstance {
        override fun ContentDrawScope.drawIndication() {
            drawContent()
        }
    }

    @Composable
    override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance {
        return NoRippleIndicationInstance
    }
}

在悬浮视图的Composable组件中使用该Indication:

Button(
    onClick = { /* 点击逻辑 */ },
    indication = NoRippleIndication,
    interactionSource = remember { MutableInteractionSource() }
) {
    Text("返回")
}

总结

优先推荐方案1,主动清除动画与Drawable回调是最稳妥的方式,能从根源避免动画在视图脱离后执行导致的崩溃。如果是特定的涟漪问题,方案3的自定义Indication也能彻底解决问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 23:27:49