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
相关产品推荐
相关产品推荐

