如何在悬浮窗中使用 Compose?

如何在悬浮窗中使用 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 {
}
相关推荐
2501_9159214310 小时前
iOS 26 崩溃日志解析,新版系统下崩溃获取与诊断策略
android·ios·小程序·uni-app·cocoa·iphone·策略模式
齊家治國平天下12 小时前
Android 14 Input 事件派发机制深度剖析
android·input·hal
2501_9160137413 小时前
iOS 推送开发完整指南,APNs 配置、证书申请、远程推送实现与上架调试经验分享
android·ios·小程序·https·uni-app·iphone·webview
李艺为15 小时前
非预置应用使用platform签名并且添加了android.uid.system无法adb安装解决方法
android·adb
李宥小哥17 小时前
C#基础11-常用类
android·java·c#
Jerry1 天前
Compose 中的绘制功能简介
android
我科绝伦(Huanhuan Zhou)1 天前
【脚本升级】银河麒麟V10一键安装MySQL9.3.0
android·adb
消失的旧时光-19431 天前
Android回退按钮处理方法总结
android·开发语言·kotlin
叫我龙翔1 天前
【MySQL】从零开始了解数据库开发 --- 数据表的约束
android·c++·mysql·数据库开发