如何在悬浮窗中使用 Compose?
显示悬浮窗
声明悬浮窗权限。
XML
<manifest>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
</manifest>
判断是否拥有显示权限。
Kotlin
fun hasOverlayPermission(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Settings.canDrawOverlays(weakReference.get())
} else {
// Android 23 以下,应用默认是有权限的。
true
}
}
打开悬浮窗显示权限设置页
Kotlin
fun enterSettings(activity: Activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
activity.startActivity(
Intent(
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
"package:${activity.packageName}".toUri()
)
)
} else {
// Android 23 以下,应用默认是有权限的,无需申请,也没有设置界面。
}
}
获取 WindowManager
Kotlin
val windowManager: WindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
创建 WindowManager.LayoutParams
Kotlin
val layoutParams = WindowManager.LayoutParams().apply {
format = PixelFormat.RGBA_8888
flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
gravity = Gravity.START or Gravity.TOP
width = WindowManager.LayoutParams.MATCH_PARENT
height = WindowManager.LayoutParams.MATCH_PARENT
x = 0
y = 0
windowAnimations = android.R.style.Animation_Dialog
// 根据API版本设置窗口类型,优先使用TYPE_APPLICATION_OVERLAY(API 26+),低版本使用TYPE_SYSTEM_ALERT
type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
} else {
WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
}
}
显示悬浮窗视图
Kotlin
fun show() {
if (isShowing) return
if (!hasOverlayPermission()) {
return
}
try {
weakReference.get()?.let { context ->
windowManager.addView(TextView(context).apply {
text = "悬浮窗已经打开"
}, layoutParams)
isShowing = true
}
} catch (e: Exception) {
isShowing = false
e.printStackTrace()
}
}
使用 Compose 显示有什么问题?
直接换用 Compose 显示会报错。
Kotlin
windowManager.addView(ComposeView(context).apply {
setContent {
Text("悬浮窗已经打开")
}
}, layoutParams)
会抛出 ViewTreeLifecycleOwner not found from androidx.compose.ui.platform.ComposeView 异常。
自定义 LifecycleOwner 来解决本问题。
MyLifecycleOwner
Kotlin
inner class MyLifecycleOwner : SavedStateRegistryOwner, ViewModelStoreOwner {
private val lifecycleRegistry = LifecycleRegistry(this)
private val savedStateRegistryController = SavedStateRegistryController.create(this)
private var view: View? = null
@Volatile
private var viewModelStoreInstance: ViewModelStore? = null
override val lifecycle: Lifecycle
get() = lifecycleRegistry
override val savedStateRegistry: SavedStateRegistry
get() = savedStateRegistryController.savedStateRegistry
override val viewModelStore: ViewModelStore
get() = viewModelStoreInstance ?: synchronized(this) {
viewModelStoreInstance ?: ViewModelStore().also {
viewModelStoreInstance = it
}
}
fun performRestore(savedState: Bundle?) {
savedStateRegistryController.performRestore(savedState)
}
fun onCreate() {
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
}
fun onStart() {
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
}
fun onResume() {
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
}
fun onPause() {
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
}
fun onStop() {
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
}
fun onDestroy() {
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
// 手动清理 ViewModelStore
viewModelStore.clear()
view?.apply {
setViewTreeLifecycleOwner(null)
setViewTreeViewModelStoreOwner(null)
setViewTreeSavedStateRegistryOwner(null)
}
view = null
}
fun attachToView(view: View) {
// 需要为 Compose 设置 ViewTreeLifecycleOwner
view.setViewTreeLifecycleOwner(this)
view.setViewTreeViewModelStoreOwner(this)
view.setViewTreeSavedStateRegistryOwner(this)
this.view = view
}
}
在悬浮窗中使用 Compose
Kotlin
var lifecycleOwner = MyLifecycleOwner()
fun show() {
if (isShowing) return
if (!hasOverlayPermission()) {
return
}
lifecycleOwner = MyLifecycleOwner()
try {
weakReference.get()?.let { context ->
windowManager.addView(ComposeView(context).apply {
// 绑定生命周期到 ComposeView
lifecycleOwner.attachToView(this)
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
lifecycleOwner.performRestore(null)
lifecycleOwner.onCreate()
lifecycleOwner.onStart()
lifecycleOwner.onResume()
setContent {
Text("悬浮窗已经打开")
}
}, layoutParams)
isShowing = true
}
} catch (e: Exception) {
lifecycleOwner.onPause()
lifecycleOwner.onStop()
lifecycleOwner.onDestroy()
isShowing = false
e.printStackTrace()
}
}
关闭悬浮窗
Kotlin
fun hide() {
if (!isShowing) return
try {
lifecycleOwner.onPause()
lifecycleOwner.onStop()
windowManager.removeView(composeView)
lifecycleOwner.onDestroy()
isShowing = false
} catch (e: Exception) {
Log.e(TAG, "Error removing view: ${e.message}")
}
}
总结
Compose 需要感知 ViewTreeLifecycleOwner。
在 androidx.activity:activity 依赖库中,androidx.activity.ComponentActivity 中同样为根 View 设置了 ViewTreeLifecycleOwner
androidx.activity.ComponentActivity#initializeViewTreeOwners
Kotlin
open fun initializeViewTreeOwners() {
window.decorView.setViewTreeLifecycleOwner(this)
window.decorView.setViewTreeViewModelStoreOwner(this)
window.decorView.setViewTreeSavedStateRegistryOwner(this)
window.decorView.setViewTreeOnBackPressedDispatcherOwner(this)
window.decorView.setViewTreeFullyDrawnReporterOwner(this)
}
Kotlin
open class ComponentActivity() : androidx.core.app.ComponentActivity(),
LifecycleOwner,
ViewModelStoreOwner,
SavedStateRegistryOwner {
}