Android悬浮窗看这篇就够了

您所在的位置:网站首页 苹果7怎么关闭屏幕悬浮窗 Android悬浮窗看这篇就够了

Android悬浮窗看这篇就够了

2023-08-07 18:16| 来源: 网络整理| 查看: 265

之前想要实现个全局全浮球的效果,找遍了网上大佬的博客,踩了不少坑,但是还是有一些问题没有解决,比如个别手机设置界面的部分二级界面无法显示(例如:MIUI设置-关于手机[狗头保命])

索性在此总结一篇关于悬浮窗使用以及适配的详细博客(Kotlin代码)

老规矩先上源码链接gitee.com/AndroidLMY/…

效果图

悬浮窗的基本原理

首先我们来说下悬浮窗的基本原理是什么

动态添加View

我们都知道我们我们想动态的添加View到界面上无非是

实例话化一个View然后添加到某个布局中 例如:

val view = LayoutInflater.from(this).inflate(R.layout.activity_float_item, null) ll_all.addView(view)

那么此时我们想在当前Activity不依赖任何布局添加View时 我们可以获取WindowManager来添加我们的View

例如:

val view = LayoutInflater.from(this).inflate(R.layout.activity_float_item, null) var layoutParam = WindowManager.LayoutParams().apply { //设置大小 自适应 width = WRAP_CONTENT height = WRAP_CONTENT } windowManager.addView(view,layoutParam)

悬浮窗原理

获取WindowManager

创建View

添加到WindowManager中

应用内悬浮窗 应用内悬浮窗实现流程

获取WindowManager

创建悬浮View

设置悬浮View的拖拽事件

添加View到WindowManager中

代码如下:

var layoutParam = WindowManager.LayoutParams().apply { //设置大小 自适应 width = WRAP_CONTENT height = WRAP_CONTENT flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE } // 新建悬浮窗控件 floatRootView = LayoutInflater.from(this).inflate(R.layout.activity_float_item, null) //设置拖动事件 floatRootView?.setOnTouchListener(ItemViewTouchListener(layoutParam, windowManager)) // 将悬浮窗控件添加到WindowManager windowManager.addView(floatRootView, layoutParam)

拖拽监听ItemViewTouchListener代码如下:

class ItemViewTouchListener(val wl: WindowManager.LayoutParams, val windowManager: WindowManager) : View.OnTouchListener { private var x = 0 private var y = 0 override fun onTouch(view: View, motionEvent: MotionEvent): Boolean { when (motionEvent.action) { MotionEvent.ACTION_DOWN -> { x = motionEvent.rawX.toInt() y = motionEvent.rawY.toInt() } MotionEvent.ACTION_MOVE -> { val nowX = motionEvent.rawX.toInt() val nowY = motionEvent.rawY.toInt() val movedX = nowX - x val movedY = nowY - y x = nowX y = nowY wl.apply { x += movedX y += movedY } //更新悬浮球控件位置 windowManager?.updateViewLayout(view, wl) } else -> { } } return false } }

效果

应用外悬浮窗(有局限性)

应用外悬浮窗实现流程 这里我使用了LivaData来进行和Service的通信

申请悬浮窗权限

创建Service

获取WindowManager

创建悬浮View

设置悬浮View的拖拽事件

添加View到WindowManager

在清单文件添加权限

上代码:

打开悬浮窗

startService(Intent(this, SuspendwindowService::class.java)) Utils.checkSuspendedWindowPermission(this) { isReceptionShow = false ViewModleMain.isShowSuspendWindow.postValue(true) }

SuspendwindowService代码如下

package com.lmy.suspendedwindow.service import android.annotation.SuppressLint import android.graphics.PixelFormat import android.os.Build import android.util.DisplayMetrics import android.view.* import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import androidx.lifecycle.LifecycleService import com.lmy.suspendedwindow.R import com.lmy.suspendedwindow.utils.Utils import com.lmy.suspendedwindow.utils.ViewModleMain import com.lmy.suspendedwindow.utils.ItemViewTouchListener /** * @功能:应用外打开Service 有局限性 特殊界面无法显示 * @User Lmy * @Creat 4/15/21 5:28 PM * @Compony 永远相信美好的事情即将发生 */ class SuspendwindowService : LifecycleService() { private lateinit var windowManager: WindowManager private var floatRootView: View? = null//悬浮窗View override fun onCreate() { super.onCreate() initObserve() } private fun initObserve() { ViewModleMain.apply { isVisible.observe(this@SuspendwindowService, { floatRootView?.visibility = if (it) View.VISIBLE else View.GONE }) isShowSuspendWindow.observe(this@SuspendwindowService, { if (it) { showWindow() } else { if (!Utils.isNull(floatRootView)) { if (!Utils.isNull(floatRootView?.windowToken)) { if (!Utils.isNull(windowManager)) { windowManager?.removeView(floatRootView) } } } } }) } } @SuppressLint("ClickableViewAccessibility") private fun showWindow() { windowManager = getSystemService(WINDOW_SERVICE) as WindowManager val outMetrics = DisplayMetrics() windowManager.defaultDisplay.getMetrics(outMetrics) var layoutParam = WindowManager.LayoutParams().apply { type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY } else { WindowManager.LayoutParams.TYPE_PHONE } format = PixelFormat.RGBA_8888 flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE //位置大小设置 width = WRAP_CONTENT height = WRAP_CONTENT gravity = Gravity.LEFT or Gravity.TOP //设置剧中屏幕显示 x = outMetrics.widthPixels / 2 - width / 2 y = outMetrics.heightPixels / 2 - height / 2 } // 新建悬浮窗控件 floatRootView = LayoutInflater.from(this).inflate(R.layout.activity_float_item, null) floatRootView?.setOnTouchListener(ItemViewTouchListener(layoutParam, windowManager)) // 将悬浮窗控件添加到WindowManager windowManager.addView(floatRootView, layoutParam) } }

ViewModleMain代码如下

package com.lmy.suspendedwindow.utils import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel /** * @功能: 用于和Service通信 * @User Lmy * @Creat 4/16/21 8:37 AM * @Compony 永远相信美好的事情即将发生 */ object ViewModleMain : ViewModel() { //悬浮窗口创建 移除 基于无障碍服务 var isShowWindow = MutableLiveData() //悬浮窗口创建 移除 var isShowSuspendWindow = MutableLiveData() //悬浮窗口显示 隐藏 var isVisible = MutableLiveData() }

Utils代码如下:

package com.lmy.suspendedwindow.utils import android.app.Activity import android.app.ActivityManager import android.content.Context import android.content.Intent import android.net.Uri import android.os.Build import android.provider.Settings import android.text.TextUtils import android.util.Log import android.widget.Toast import com.lmy.suspendedwindow.service.WorkAccessibilityService import java.util.* /** * @功能: 工具类 * @User Lmy * @Creat 4/16/21 8:33 AM * @Compony 永远相信美好的事情即将发生 */ object Utils { const val REQUEST_FLOAT_CODE=1001 /** * 跳转到设置页面申请打开无障碍辅助功能 */ private fun accessibilityToSettingPage(context: Context) { //开启辅助功能页面 try { val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK context.startActivity(intent) } catch (e: Exception) { val intent = Intent(Settings.ACTION_SETTINGS) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK context.startActivity(intent) e.printStackTrace() } } /** * 判断Service是否开启 * */ fun isServiceRunning(context: Context, ServiceName: String): Boolean { if (TextUtils.isEmpty(ServiceName)) { return false } val myManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager val runningService = myManager.getRunningServices(1000) as ArrayList for (i in runningService.indices) { if (runningService[i].service.className == ServiceName) { return true } } return false } /** * 判断悬浮窗权限权限 */ private fun commonROMPermissionCheck(context: Context?): Boolean { var result = true if (Build.VERSION.SDK_INT >= 23) { try { val clazz: Class = Settings::class.java val canDrawOverlays = clazz.getDeclaredMethod("canDrawOverlays", Context::class.java) result = canDrawOverlays.invoke(null, context) as Boolean } catch (e: Exception) { Log.e("ServiceUtils", Log.getStackTraceString(e)) } } return result } /** * 检查悬浮窗权限是否开启 */ fun checkSuspendedWindowPermission(context: Activity, block: () -> Unit) { if (commonROMPermissionCheck(cont ext)) { block() } else { Toast.makeText(context, "请开启悬浮窗权限", Toast.LENGTH_SHORT).show() context.startActivityForResult(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION).apply { data = Uri.parse("package:${context.packageName}") }, REQUEST_FLOAT_CODE) } } /** * 检查无障碍服务权限是否开启 */ fun checkAccessibilityPermission(context: Activity, block: () -> Unit) { if (isServiceRunning(context, WorkAccessibilityService::class.java.canonicalName)) { block() } else { accessibilityToSettingPage(context) } } fun isNull(any: Any?): Boolean = any == null }

效果:

悬浮窗权限的适配 权限配置和请求

这一块倒是没什么坑

在当Android7.0以上的时候,需要在AndroidManefest.xml文件中声明SYSTEM_ALERT_WINDOW权限

LayoutParam的坑!!!!

WindowManager的addView方法有两个参数,一个是需要加入的控件对象,另一个参数是WindowManager.LayoutParam对象。

LayoutParam里的type变量。有大坑!!!!!!,这个变量是用来指定窗口类型的。在设置这个变量时,需要对不同版本的Android系统进行适配。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; } else { layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE; }

在Android 8.0之前,悬浮窗口设置可以为TYPE_PHONE,这种类型是用于提供用户交互操作的非应用窗口。

但是Android 8.0以上版本你继续使用TYPE_PHONE类型的悬浮窗口,则会出现如下异常信息:

android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRootImpl$W@f8ec928 -- permission denied for window type 2002

Android 8.0以后不允许使用一下窗口类型来在其他应用和窗口上方显示提醒窗口,这些类型包括:

TYPE_PHONE TYPE_PRIORITY_PHONE TYPE_SYSTEM_ALERT TYPE_SYSTEM_OVERLAY TYPE_SYSTEM_ERROR

如果需要实现在其他应用和窗口上方显示提醒窗口,那么必须该为TYPE_APPLICATION_OVERLAY的类型。

但是这个TYPE_APPLICATION_OVERLAY类型无法在所有界面上进行显示

就像是这样

有的同学会问这什么鬼操作啊?这怎么解决

不要慌 只要耐心找总会找到的答案的 百度不行那就谷歌

经过不懈的努力终于找到了解决办法

使用另外一个类型TYPE_ACCESSIBILITY_OVERLAY就可以解决此问题

但是当你开开心心使用的时候你会发现以下错误

经过查证一些资料之后发现这个类型必须和无障碍 AccessibilityService搭配使用

无障碍悬浮窗

配置无障碍流程见我另一篇博客基于无障碍服务实现自动跳过APP启动页广告

无障碍悬浮窗实现流程

配置无障碍服务

在AccessibilityService中获取WindowManager

创建悬浮View

设置悬浮View的拖拽事件

添加View到WindowManager

启动悬浮窗:

Utils.checkAccessibilityPermission(this) { ViewModleMain.isShowWindow.postValue(true) }

WorkAccessibilityService代码如下

package com.lmy.suspendedwindow.service import android.accessibilityservice.AccessibilityService import android.annotation.SuppressLint import android.content.Intent import android.graphics.PixelFormat import android.os.Build import android.util.DisplayMetrics import android.view.* import android.view.accessibility.AccessibilityEvent import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import com.lmy.suspendedwindow.R import com.lmy.suspendedwindow.utils.ItemViewTouchListener import com.lmy.suspendedwindow.utils.Utils.isNull import com.lmy.suspendedwindow.utils.ViewModleMain /** * @功能:利用无障碍打开悬浮窗口 无局限性 任何界面可以显示 * @User Lmy * @Creat 4/15/21 5:57 PM * @Compony 永远相信美好的事情即将发生 */ class WorkAccessibilityService : AccessibilityService(), LifecycleOwner { private lateinit var windowManager: WindowManager private var floatRootView: View? = null//悬浮窗View private val mLifecycleRegistry = LifecycleRegistry(this) override fun onCreate() { super.onCreate() mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE); initObserve() } /** * 打开关闭的订阅 */ private fun initObserve() { ViewModleMain.isShowWindow.observe(this, { if (it) { showWindow() } else { if (!isNull(floatRootView)) { if (!isNull(floatRootView?.windowToken)) { if (!isNull(windowManager)) { windowManager?.removeView(floatRootView) } } } } }) } @SuppressLint("ClickableViewAccessibility") private fun showWindow() { // 设置LayoutParam // 获取WindowManager服务 windowManager = getSystemService(WINDOW_SERVICE) as WindowManager val outMetrics = DisplayMetrics() windowManager.defaultDisplay.getMetrics(outMetrics) var layoutParam = WindowManager.LayoutParams() layoutParam.apply { //显示的位置 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY //刘海屏延伸到刘海里面 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES } } else { type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT } flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE width = WindowManager.LayoutParams.WRAP_CONTENT height = WindowManager.LayoutParams.WRAP_CONTENT format = PixelFormat.TRANSPARENT } floatRootView = LayoutInflater.from(this).inflate(R.layout.activity_float_item, null) floatRootView?.setOnTouchListener(ItemViewTouchListener(layoutParam, windowManager)) windowManager.addView(floatRootView, layoutParam) } override fun onServiceConnected() { super.onServiceConnected() mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START) } override fun getLifecycle(): Lifecycle = mLifecycleRegistry override fun onStart(intent: Intent?, startId: Int) { super.onStart(intent, startId) mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START) } override fun onUnbind(intent: Intent?): Boolean { mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP) return super.onUnbind(intent) } override fun onDestroy() { mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) super.onDestroy() } override fun onAccessibilityEvent(event: AccessibilityEvent?) { } override fun onInterrupt() { } }

总结

使用普通的Service创建悬浮窗无法做到任何界面都能显示

利用无障碍服务可以做到任何界面悬浮



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3