Android 弹窗浅谈

我们知道 Android 弹窗中,有一类弹窗会在应用之外也显示,这是因为他被申明成了系统弹窗,除此之外还有2类弹窗分别是:子弹窗应用弹窗

  • 应用弹窗:就是我们常规使用的 Dialog 之类弹窗,依赖于应用的 Activity;
  • 子弹窗:依赖于父窗口,比如 PopupWindow;
  • 系统弹窗:比如状态栏、Toast等,本文所讲的系统悬浮窗就是系统弹窗。

系统悬浮窗具体实现

权限申请

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />

代码设计

下面的包结构截图,简单展示了实现系统悬浮窗的代码结构,更复杂的业务需要可在此基础上进行扩展。

  • FloatWindowService:系统悬浮窗在此 Service 中弹出;
  • FloatWindowManager:系统悬浮窗管理类;
  • FloatLayout:系统悬浮窗布局;
  • HomeKeyObserverReceiver:监听 Home 键;
  • FloatWindowUtils:系统悬浮窗工具类。

具体实现

FloatWindowService 类

class FloatWindowService : Service() {private val TAG = FloatWindowService::class.java.simpleNameprivate var mFloatWindowManager: FloatWindowManager? = nullprivate var mHomeKeyObserverReceiver: HomeKeyObserverReceiver? = nulloverride fun onCreate() {TLogUtils.i(TAG, "onCreate: ")mFloatWindowManager = FloatWindowManager(applicationContext)mHomeKeyObserverReceiver = HomeKeyObserverReceiver()registerReceiver(mHomeKeyObserverReceiver, IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))mFloatWindowManager!!.createWindow()}override fun onBind(intent: Intent?): IBinder? {return null}override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {return START_NOT_STICKY}override fun onDestroy() {TLogUtils.i(TAG, "onDestroy: ")mFloatWindowManager?.removeWindow()if (mHomeKeyObserverReceiver != null) {unregisterReceiver(mHomeKeyObserverReceiver)}}}

FloatWindowManager 类

包括系统悬浮窗的创建、显示、销毁(以及更新)。


public void addView(View view, ViewGroup.LayoutParams params); // 添加 View 到 Window
public void updateViewLayout(View view, ViewGroup.LayoutParams params); //更新 View 在 Window 中的位置
public void removeView(View view); //删除 View

FloatWindowManager 类代码

class FloatWindowManager constructor(context: Context) {var isShowing = falseprivate val TAG = FloatWindowManager::class.java.simpleNameprivate var mContext: Context = contextprivate var mFloatLayout = FloatLayout(mContext)private var mLayoutParams: WindowManager.LayoutParams? = nullprivate var mWindowManager: WindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManagerfun createWindow() {TLogUtils.i(TAG, "createWindow: start...")// 对象配置操作使用apply,额外的处理使用alsomLayoutParams = WindowManager.LayoutParams().apply {type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {// Android 8.0以后需要使用TYPE_APPLICATION_OVERLAY,不允许使用以下窗口类型来在其他应用和窗口上方显示提醒窗口:TYPE_PHONE、TYPE_PRIORITY_PHONE、TYPE_SYSTEM_ALERT、TYPE_SYSTEM_OVERLAY、TYPE_SYSTEM_ERROR。WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY} else {// 在Android 8.0之前,悬浮窗口设置可以为TYPE_PHONE,这种类型是用于提供用户交互操作的非应用窗口。// 在API Level  = 23的时候,需要在Android Manifest.xml文件中声明权限SYSTEM_ALERT_WINDOW才能在其他应用上绘制控件WindowManager.LayoutParams.TYPE_PHONE}// 设置图片格式,效果为背景透明format = PixelFormat.RGBA_8888// 设置浮动窗口不可聚焦(实现操作除浮动窗口外的其他可见窗口的操作)flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE// 调整悬浮窗显示的停靠位置为右侧置顶gravity = Gravity.TOP or Gravity.ENDwidth = 800height = 200x = 20y = 40}mWindowManager.addView(mFloatLayout, mLayoutParams)TLogUtils.i(TAG, "createWindow: end...")isShowing = true}fun showWindow() {TLogUtils.i(TAG, "showWindow: isShowing = $isShowing")if (!isShowing) {if (mLayoutParams == null) {createWindow()} else {mWindowManager.addView(mFloatLayout, mLayoutParams)isShowing = true}}}fun removeWindow() {TLogUtils.i(TAG, "removeWindow: isShowing = $isShowing")mWindowManager.removeView(mFloatLayout)isShowing = false}}

FloatLayout 类及其 Layout

系统悬浮窗自定义View:FloatLayout

class FloatLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0) :ConstraintLayout(context, attrs, defStyleAttr, defStyleRes) {private var mTime: TCLTextViewprivate var mDistance: TCLTextViewprivate var mSpeed: TCLTextViewprivate var mCalories: TCLTextViewinit {val view = LayoutInflater.from(context).inflate(R.layout.do_exercise_view_float_layout, this, true)mTime = view.findViewById(R.id.float_layout_tv_time)mDistance = view.findViewById(R.id.float_layout_tv_distance)mSpeed = view.findViewById(R.id.float_layout_tv_speed)mCalories = view.findViewById(R.id.float_layout_tv_calories)}}

布局文件:float_layout_tv_time

HomeKeyObserverReceiver 类

class HomeKeyObserverReceiver : BroadcastReceiver() {override fun onReceive(context: Context?, intent: Intent?) {try {val action = intent!!.actionval reason = intent.getStringExtra("reason")TLogUtils.d(TAG, "HomeKeyObserverReceiver: action = $action,reason = $reason")if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS == action && "homekey" == reason) {val keyCode = intent.getIntExtra("keycode", KeyEvent.KEYCODE_UNKNOWN)TLogUtils.d(TAG, "keyCode = $keyCode")context?.stopService(Intent(context, FloatWindowService::class.java))}} catch (ex: Exception) {ex.printStackTrace()}}companion object {private val TAG = HomeKeyObserverReceiver::class.java.simpleName}}

FloatWindowUtils 类

object FloatWindowUtils {const val REQUEST_FLOAT_CODE = 1000private val TAG = FloatWindowUtils::class.java.simpleName/*** 判断Service是否开启*/fun isServiceRunning(context: Context, ServiceName: String): Boolean {if (TextUtils.isEmpty(ServiceName)) {return false}val myManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManagerval runningService = myManager.getRunningServices(1000) as ArrayList<ActivityManager.RunningServiceInfo>runningService.forEach {if (it.service.className == ServiceName) {return true}}return false}/*** 检查悬浮窗权限是否开启*/@SuppressLint("NewApi")fun checkSuspendedWindowPermission(context: Activity, block: () -> Unit) {if (commonROMPermissionCheck(context)) {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 commonROMPermissionCheck(context: Context?): Boolean {var result = trueif (Build.VERSION.SDK_INT >= 23) {try {val clazz: Class<*> = Settings::class.javaval canDrawOverlays = clazz.getDeclaredMethod("canDrawOverlays", Context::class.java)result = canDrawOverlays.invoke(null, context) as Boolean} catch (e: Exception) {TLogUtils.e(TAG, e)}}return result}}

使用

            FloatWindowUtils.checkSuspendedWindowPermission(this) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {startForegroundService(Intent(this, FloatWindowService::class.java))} else {startService(Intent(this, FloatWindowService::class.java))}}

总结

本文并未详细讨论系统悬浮窗的拖动功能,实现系统悬浮穿基本功能可以总结为以下几个步骤:

1. 声明及申请权限;
2. 构建悬浮窗需要的控件 Service、Receiver、Manager、Layout、Util;
3. 使用 WindowManager 创建、显示、销毁(以及更新)Layout。

Kotlin 实现 Android 系统悬浮窗相关推荐

  1. 8、Android 系统悬浮窗实现

    Android悬浮窗的简单实现_普通网友的博客-CSDN博客_android悬浮窗实现 Android悬浮窗的实现(易错点) https://www.jianshu.com/p/6ca8272d90e ...

  2. 教你如何在Android 6.0上创建系统悬浮窗

    郭霖大神的文章:http://mp.weixin.qq.com/s?__biz=MzA5MzI3NjE2MA==&mid=2650235949&idx=1&sn=0f7eded ...

  3. Android M及以上版本系统 悬浮窗权限 的解决方案

    Android M及以上版本系统 悬浮窗权限 的解决方案 Android的窗口体系中,WindowManager占有非常重要的地位,平时我们使用悬浮窗会遇到一些权限的问题. 当 Android工程在 ...

  4. android悬浮窗 tab,Android WindowManager悬浮窗

     Android WindowManager悬浮窗 Android WindowManager悬浮窗的实现代码不难,悬浮窗创建成功后将悬浮悬停在设备的屏幕桌面上.悬浮窗通常使用情况:假设APP需要 ...

  5. android 实现悬浮窗相机后台视频隐秘录制

    android 实现悬浮窗相机后台视频隐秘录制 GitHub上参考了别人做悬浮窗的代码,后面自己加的的相机录像功能 主要功能: 1.悬浮窗录制视频,可实现后台或锁屏使用摄像头录制视频. 2.可自定义悬 ...

  6. Android桌面悬浮窗效果实现,仿360手机卫士悬浮窗效果

    转载自:http://blog.csdn.net/guolin_blog/article/details/8689140 大家好,今天给大家带来一个仿360手机卫士悬浮窗效果的教程,在开始之前请允许我 ...

  7. Android设置悬浮窗按钮,图片有多余的白色背景

    Android设置系统悬浮窗简单的操作就是: 1.设置悬浮窗显示的样式(用LayoutInflater实例化layout布局) 2.设置该样式在Window中的显示样式(即 WindowManager ...

  8. Android桌面悬浮窗进阶,QQ手机管家小火箭效果实现

    这次我们将代码的重点放在火箭升空的效果上,因此简单起见,就直接在模仿360手机卫士悬浮窗的那份代码的基础上继续开发了,如果你还没有看过那篇文章的话,建议先去阅读 Android桌面悬浮窗效果实现,仿3 ...

  9. android动态申请悬浮框权限,Android创建悬浮窗的完整步骤

    在Android中想要创建悬浮窗分为三步 1.申请权限 2.使用服务启动悬浮窗 3.设置悬浮窗参数并添加进WindowManager 下面话不多说了,来一起看看详细的实现过程 申请权限 首先需要申请悬 ...

最新文章

  1. 谷歌AI乳腺癌检测超过人类,LeCun质疑引起讨论
  2. 话说placeholder
  3. Redis简单动态字符串
  4. go面向对象编程:结构体struct详解、结构体实例的创建方式、结构体之间的转换(type取别名的使用)、方法的注意事项及与函数的区别
  5. QT学习:QAxObject对象访问
  6. golang常用库:字段参数验证库-validator使用
  7. hive日志位置(日志定位报错:Failed with exception Unable to move sourcehdfs://namenode/tmp/hive-pmp_bi/h)...
  8. 学习笔记10-C语言-小项目-五子棋
  9. python不同数据类型的式子_Python 基础篇:数据类型、数据运算、表达
  10. 电子计算机解锁,全电子计算机联锁系统信号解锁模块的研究
  11. [目标检测]YOLO原理
  12. C++中Lambda函数(匿名函数)
  13. 【转】在birt中显示条形码
  14. 软著申请说明书及源程序模板
  15. 怀念经理用鼠标线联网的1990年代当时
  16. Learn C++学习笔记:第M章—最常用的智能指针:std::unique_ptr std::make_unique
  17. 拼多多电商外部工具(浏览器插件)
  18. mySQL:Access denied for user 'root'@'127.0.0.1' to database 'information_schema'
  19. CTF题库RSA实践 (RSA-Tool2 by tE! 工具的使用)
  20. 修改Switch开关按钮的颜色

热门文章

  1. python 列表 笛卡尔积_python-列表字典的笛卡尔积
  2. go语言后端框架之简单探索
  3. CES智能硬件新品TOP 10:科幻电影穿越到现实
  4. python颜色识别_颜色检测python
  5. ARM架构服务器centos7.4上yum安装k8s教程
  6. WCF 配置服务 演示
  7. Stata的多元线性回归与泊松回归
  8. Python随机游走模型
  9. sqlserver varchar 类型存储生僻字,会变成问号,而nvarchar类型不会 是什么原理? (㙍、㮾,䶮)
  10. IC设计公司登陆创业板:难、难、难