版权声明
本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/16497765.html
本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。
前言
此博客讲解的悬浮窗开发,悬浮窗开发需要众多权限。首先就需要在设置-应用-权限里被允许弹出悬浮窗。
使用服务或者Application启动悬浮窗
悬浮窗部分需要注意的地方,Service与Application的Context是没有前台View的(如果你传入的是正在运行的Activity的Context可以不关注下面TYPE_SYSTEM_ALERT部分)。所以,你如果不增加
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
或者
layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
就会出现如下报错:
Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
所以,如果你需要使用服务启动悬浮窗layoutParams,必定需要根据Android版本添加上面的其中一项。 另外你还需要在AndroidManifest.xml中添加权限:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
关于TYPE_SYSTEM_ALERT的官方注解如下:
/** 窗口类型: 应用程序覆盖窗口显示在所有活动窗口上方(类型介于 FIRST_APPLICATION_WINDOW 和 LAST_APPLICATION_WINDOW 之间), 但在状态栏或 IME 等关键系统窗口下方。系统可以随时更改这些窗口的位置、大小或可见性,以减少用户的视觉混乱并管理资源。 需要 android.Manifest.permission.SYSTEM_ALERT_WINDOW 权限。 系统将调整具有此窗口类型的进程的重要性,以减少低内存杀手杀死它们的机会。在多用户系统中,仅在拥有用户的屏幕上显示。 */
悬浮窗的layoutParams.flags
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | 表示Window不需要获取焦点,也不需要接受各种输入事件,此标记会同时启用FLAG_NOT_TOUCH_MODAL,最终事件会直接传递给下层的具有焦点的Window |
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | 在此模式下,系统会将当前Window区域以外的单击事件传递给底层的Window,当前Window区域以内的单击事件则自己处理。这个标记很重要,一般来说都需要开启此标记,否则其他Window将无法收到单击事件 |
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | 开启此模式可以让Window显示在锁屏的界面上 |
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | 保存屏幕常亮 |
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | 允许窗口延伸到屏幕之外 |
WindowManager.LayoutParams.FLAG_DIM_BEHIND | 此窗口后面的所有内容都将变暗。使用 dimAmount 来控制暗淡的量 |
WindowManager.LayoutParams.FLAG_FULLSCREEN | 全屏显示 |
WindowManager.LayoutParams.FLAG_SECURE | 安全模式,将窗口内容视为安全,防止其出现在屏幕截图中或在非安全显示器上查看 |
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED | 硬件加速 |
实现代码
代码则很简单,关键点下面有注释
import android.app.Service import android.content.Context import android.content.Intent import android.graphics.PixelFormat import android.os.Build import android.os.IBinder import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.WindowManager import com.lwlx.ai.databinding.AiDialogAiBinding import com.lwlx.common.expand.setOnIntervalClickListener class AiService : Service() { val binding by lazy { AiDialogAiBinding.inflate(LayoutInflater.from(application)) } val windowManager by lazy { application.getSystemService(WINDOW_SERVICE) as WindowManager } companion object { /** * 启动服务 */ fun startService(context: Context) { val intent = Intent(context, AiService::class.java) context.startService(intent) } } override fun onBind(intent: Intent): IBinder { TODO("Return the communication channel to the service.") } override fun onCreate() { super.onCreate() } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { initView() showView() return super.onStartCommand(intent, flags, startId) } private fun initView(){ binding.button.setOnIntervalClickListener { hideView() } } fun showView() { windowManager.addView(binding.root, getWindowLayoutParams()) } fun hideView() { windowManager.removeView(binding.root) } private fun getWindowLayoutParams(): WindowManager.LayoutParams { val layoutParams = WindowManager.LayoutParams() //设置宽高 layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT layoutParams.height = 300 //设置显示位置 layoutParams.gravity = Gravity.BOTTOM or Gravity.END layoutParams.format = PixelFormat.RGBA_8888 //设置状态栏与导航栏效果 layoutParams.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or View.SYSTEM_UI_FLAG_IMMERSIVE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION //设置悬浮窗效果 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY } else { layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or WindowManager.LayoutParams.FLAG_FULLSCREEN } return layoutParams } }
使用最新的Compose开发悬浮窗
其他与上面的一样,难点是Compose是需要生命周期的,通常情况下这个生命周期是Activity提供的,但是在后台服务中没有这样的生命周期。所以我们需要自己创建生命周期类并且自己控制。
import android.os.Bundle
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import androidx.savedstate.SavedStateRegistry
import androidx.savedstate.SavedStateRegistryController
import androidx.savedstate.SavedStateRegistryOwner
class ComposeViewLifecycle :LifecycleOwner, SavedStateRegistryOwner {
private val savedStateRegistryController = SavedStateRegistryController.create(this)
override val savedStateRegistry: SavedStateRegistry = savedStateRegistryController.savedStateRegistry
private val lifecycleRegistry = LifecycleRegistry(this)
override val lifecycle: Lifecycle get() = lifecycleRegistry
init {
savedStateRegistryController.performRestore(null)
// 初始化 Lifecycle 为 RESUMED 状态
lifecycleRegistry.currentState = Lifecycle.State.RESUMED
}
fun onDestroy() {
lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
}
fun performSave(outBundle: Bundle) {
savedStateRegistryController.performSave(outBundle)
}
}
在悬浮窗中导入使用
private fun showMemoryInfoWindow() {
mWindowManager.addView(getWindowView(), getWindowLayoutParams())
}
private fun hideMemoryInfoWindow() {
if(isStart) {
mWindowManager.removeView(getWindowView())
mComposeView = null
}
}
private fun updateMemoryInfo(mode:Int) {
if(mode == 0){
mLayoutParams.width = 50
mLayoutParams.height = 50
} else {
mLayoutParams.width = WindowManager.LayoutParams.MATCH_PARENT
mLayoutParams.height = 400
}
mWindowManager.updateViewLayout(getWindowView(), mLayoutParams)
}
private fun getWindowView(): ComposeView {
if (mComposeView == null) {
val compose = ComposeViewLifecycle()
mComposeView = ComposeView(mApplication).apply {
setViewTreeLifecycleOwner(compose)
setViewTreeSavedStateRegistryOwner(compose)
}
mComposeView?.setContent {
MemoryView()
}
}
return mComposeView!!
}
End
本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/16497765.html
本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。