版权声明
本文来自博客园,作者:观心静 ,转载请注明原文链接: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
本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。
来源:https://www.cnblogs.com/guanxinjing/p/16497765.html |