在安卓悬浮窗中使用Compose
在安卓开发中,实现悬浮窗的功能是非常常见的。但如果我们想要在悬浮窗中使用Jetpack Compose进行UI组件的构建,则会遇到一些挑战。本文详细介绍了如何解决这些问题,包括创建一个自定义的LifecycleOwner、设置ViewTreeLifecycleOwner以及实现ViewTreeSavedStateRegistryOwner等步骤。
安卓中实现悬浮窗的方式有很多,最常见的一种就是通过WindowManager的addView方法,这里可以参考这篇文章
windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
windowManager?.addView(view, layoutParams)
那么,能在这个悬浮窗中使用Compose吗?
众所周知Compose和View系统还是很好结合的,只要把ComposeView添加到View树上,即
private fun createView(context: Context): View {
val view = ComposeView(context).also {
it.layoutParams = LayoutParams(300.dp, 200.dp)
it.setContent {
Text("Hello World!")
}
}
return FrameLayout(context).also {
it.addView(view)
}
}
当然事情没有这么顺利
2025-02-09 16:35:56.080 16874-16874 AndroidRuntime top.ntutn.floatcompose E FATAL EXCEPTION: main
Process: top.ntutn.floatcompose, PID: 16874
java.lang.IllegalStateException: ViewTreeLifecycleOwner not found from android.widget.FrameLayout{5b7aba6 VFE...C.. ......I. 0,0-0,0}
at androidx.compose.ui.platform.WindowRecomposer_androidKt.createLifecycleAwareWindowRecomposer(WindowRecomposer.android.kt:352)
at androidx.compose.ui.platform.WindowRecomposer_androidKt.createLifecycleAwareWindowRecomposer$default(WindowRecomposer.android.kt:325)
at androidx.compose.ui.platform.WindowRecomposerFactory$Companion.LifecycleAware$lambda$0(WindowRecomposer.android.kt:168)
at androidx.compose.ui.platform.WindowRecomposerFactory$Companion.$r8$lambda$FWAPLXs0qWMqekhMr83xkKattCY(Unknown Source:0)
at androidx.compose.ui.platform.WindowRecomposerFactory$Companion$$ExternalSyntheticLambda0.createRecomposer(D8$$SyntheticClass:0)
at androidx.compose.ui.platform.WindowRecomposerPolicy.createAndInstallWindowRecomposer$ui_release(WindowRecomposer.android.kt:224)
at androidx.compose.ui.platform.WindowRecomposer_androidKt.getWindowRecomposer(WindowRecomposer.android.kt:300)
at androidx.compose.ui.platform.AbstractComposeView.resolveParentCompositionContext(ComposeView.android.kt:244)
at androidx.compose.ui.platform.AbstractComposeView.ensureCompositionCreated(ComposeView.android.kt:251)
at androidx.compose.ui.platform.AbstractComposeView.onAttachedToWindow(ComposeView.android.kt:283)
at android.view.View.dispatchAttachedToWindow(Unknown Source:82)
at android.view.ViewGroup.dispatchAttachedToWindow(Unknown Source:7)
at android.view.ViewGroup.dispatchAttachedToWindow(Unknown Source:36)
at android.view.ViewRootImpl.performTraversals(Unknown Source:219)
at android.view.ViewRootImpl.doTraversal(Unknown Source:36)
at android.view.ViewRootImpl$TraversalRunnable.run(Unknown Source:7)
at android.view.Choreographer$CallbackRecord.run(Unknown Source:20)
at android.view.Choreographer$CallbackRecord.run(Unknown Source:20)
at android.view.Choreographer.doCallbacks(Unknown Source:110)
at android.view.Choreographer.doFrame(Unknown Source:524)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Unknown Source:11)
at android.os.Handler.handleCallback(Unknown Source:2)
at android.os.Handler.dispatchMessage(Unknown Source:4)
at android.os.Looper.loopOnce(Unknown Source:182)
at android.os.Looper.loop(Unknown Source:82)
at android.app.ActivityThread.main(Unknown Source:123)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(Unknown Source:11)
at com.android.internal.os.ZygoteInit.main(Unknown Source:312)
需要一个ViewTreeLifecycleOwner,好说,我们实现一个就行了
class FloatingWindowLifecycleOwner : LifecycleOwner {
private val registry = LifecycleRegistry(this)
override val lifecycle: Lifecycle = registry
fun onShow() {
registry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
registry.handleLifecycleEvent(Lifecycle.Event.ON_START)
registry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
}
fun onDismiss() {
registry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
registry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
registry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
}
}
然而,即使把这个LifecycleOwner设置到ComposeView的脸上,他仍然跟你说找不到
那这找不到肯定是没认真找,所以查看代码,发现Compose只会从 R.id.content
开始找
那么只能玩阴的了
private fun createView(context: Context): View {
val lifecycleOwner = FloatingWindowLifecycleOwner()
viewTreeLifecycleOwner = lifecycleOwner
val view = ComposeView(context).also {
it.layoutParams = LayoutParams(300.dp, 200.dp)
it.setContent {
Text("Hello World!")
}
}
return FrameLayout(context).also {
it.addView(view)
// dirty hack, ComposeView need this
it.id = android.R.id.content
it.setViewTreeLifecycleOwner(lifecycleOwner)
}
}
再次运行,然后还是没跑起来
2025-02-09 17:08:15.591 16879-16879 AndroidRuntime top.ntutn.floatcompose E FATAL EXCEPTION: main
Process: top.ntutn.floatcompose, PID: 16879
java.lang.IllegalStateException: Composed into the View which doesn't propagateViewTreeSavedStateRegistryOwner!
at androidx.compose.ui.platform.AndroidComposeView.onAttachedToWindow(AndroidComposeView.android.kt:1352)
at android.view.View.dispatchAttachedToWindow(Unknown Source:82)
at android.view.ViewGroup.dispatchAttachedToWindow(Unknown Source:7)
at android.view.ViewGroup.dispatchAttachedToWindow(Unknown Source:36)
at android.view.ViewGroup.dispatchAttachedToWindow(Unknown Source:36)
at android.view.ViewRootImpl.performTraversals(Unknown Source:219)
at android.view.ViewRootImpl.doTraversal(Unknown Source:36)
at android.view.ViewRootImpl$TraversalRunnable.run(Unknown Source:7)
at android.view.Choreographer$CallbackRecord.run(Unknown Source:20)
at android.view.Choreographer$CallbackRecord.run(Unknown Source:20)
at android.view.Choreographer.doCallbacks(Unknown Source:110)
at android.view.Choreographer.doFrame(Unknown Source:524)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Unknown Source:11)
at android.os.Handler.handleCallback(Unknown Source:2)
at android.os.Handler.dispatchMessage(Unknown Source:4)
at android.os.Looper.loopOnce(Unknown Source:182)
at android.os.Looper.loop(Unknown Source:82)
at android.app.ActivityThread.main(Unknown Source:123)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(Unknown Source:11)
at com.android.internal.os.ZygoteInit.main(Unknown Source:312)
这回是缺了一个ViewTreeSavedStateRegistryOwner
,这个是切换横竖屏等情况恢复状态的,悬浮窗不考虑这些的话,还是照葫芦画瓢搞个空实现。
补齐之后,终于在悬浮窗看到了Compose的组件,可喜可贺。
最后代码在这里,开工大吉!
最后修改于 2025-02-09