一、WindowManger addView 增加窗口视图

1.1、步骤:

  • 创立悬浮View实例,设置View的点击,触摸等等事情
  • 创立WindowManager.LayoutParams实例
  • 获取WindowManager实例目标,调用addView办法将视图增加到窗口中

代码示例:

//1.创立View实例
val imageView = ImageView(context)
imageView.setImageResource(R.drawable.icon_joker_doge)
//2.创立WindowManager.LayoutParams实例
val lp = WindowManager.LayoutParams().apply {
    width = 200
    height = 200
		//不指定,默认值为TYPE_APPLICATION
    type = WindowManager.LayoutParams.TYPE_APPLICATION
    flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
}
//3.获取WindowManager实例目标,调用addView办法将视图增加到窗口中
requireActivity().windowManager.addView(imageView, lp)

上述代码即可增加一个悬浮View到Window,有两个参数比较重要typeflags,下面别离阐明下这两个参数

1.2、flags标识

多选项,常用的几个如下:

flag 阐明
FLAG_NOT_FOCUSABLE 表明 Window 不需求获取焦点,也不需求各种输入事情,此标记通同时启用 FLAG_NOT_TOUCH_MODAL。终究事情会直接传递给下层具有焦点的 Window。
FLAG_NOT_TOUCH_MODAL 将 Window 区域以外的单击事情传递给底层的 Window,当前 Window 内的单击事情自己处理, 一般都要敞开此事情,不然其他 Window 无法收到单击事情
FLAG_SHOW_WHEN_LOCKED 能够将 Window 显现在锁屏的界面上
FLAG_TURN_SCREEN_ON Window 显现时将屏幕点亮

其他Flag意义:WindowManager.LayoutParams的各种flag意义

1.3、TYPE窗口属性

type参数表明Window的类型,共有三种类型

  • 运用窗口(Application Window)

    取值规模 1~99

    常量 常量值 阐明
    FIRST_APPLICATION_WINDOW 1 运用窗口类型从1开端
    TYPE_BASE_APPLICATION 1 根底窗口,一切其他类型的运用窗口将出现在它的顶部;activity会运用该类型
    TYPE_APPLICATION 2 一般运用窗口;dialog会运用此类型
    TYPE_APPLICATION_STARTING 3 运用程序启动时显现的特殊运用程序窗口。不供运用程序自身运用;体系运用它来显现某些内容,直到运用程序能够显现自己的窗口
    TYPE_DRAWN_APPLICATION 4 TYPE_APPLICATION 的变体,确保窗口管理器将在显现运用程序之前等候绘制此窗口
    LAST_APPLICATION_WINDOW 99 运用窗口类型到99完毕

    正常情况下咱们自定义运用等级的窗口运用TYPE_APPLICATION 即可,其他类型基本用不到,当然咱们也能够指定Type为其他数值,完成作用相同

  • 子窗口(Sub Window)

    取值规模:1000~1999

    阐明:有必要依附在其他窗口上,比方PopWindow有必要依附Activity

    常量 常量值 阐明
    FIRST_SUB_WINDOW 1000 子窗口类型从1000开端
    TYPE_APPLICATION_PANEL 1000 运用程序窗口顶部的面板。显现在其附加窗口的顶部
    LAST_SUB_WINDOW 1999 完毕
  • 体系窗口(System Window)

    取值规模:2000~2999

    阐明:需求声明权限才能创立的窗口,比方Toast,体系状态栏,软键盘等等都是体系窗口

    常量 常量值 阐明
    FIRST_SYSTEM_WINDOW 2000 体系窗口类型从2000开端
    TYPE_STATUS_BAR 2000 状态栏窗口只能有一个;它坐落屏幕的顶部,一切其他窗口都向下移动,因而它们坐落它的下方。在多用户体系中显现在一切用户的窗口上。
    …其他
    LAST_SYSTEM_WINDOW 2999 完毕

详细的各个窗口类型值阐明能够检查这篇文章:Android悬浮窗等级

总结

  • 由于官方露出给咱们的办法只能经过WindowManger addView 的办法去新增展现视图,因而对咱们运用层开发来讲不论设置运用窗口类型,还是子窗口类型,终究出现的作用是没有区别的。终究结果都是往已经存在的窗口根底上去新增一个窗口,例如运用ActivityWindowManger去增加,只能在当前Activity中展现。

  • 特别注意的是一切上述窗口类型需求在Activity finish时分调用removeViewImmediate办法移除,不然会抛出android.view.WindowLeaked反常。这和DialogPopWindow是相同的,需求在父窗口销毁先调用dismiss办法移除

  • 假如仅仅是想在某个Acitivity中增加一个悬浮View(大部分需求是这样),直接在对应的Activity的相关视图上增加悬浮子视图View即可,并不需求运用到WindowManger addView的办法。一切该办法比较多的运用场景是增加运用层级或者体系层级的视图,这就需求用的体系窗口类型了 详细运用如下:二、体系层级悬浮View(体系窗口)

二、体系层级悬浮View(体系窗口)

2.1、步骤:

a、声明权限和申请悬浮窗权限

  • manifest文件声明权限

    <!-- 显现体系窗口权限 -->
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
    <!-- 在 屏幕最顶部显现addview-->
    <uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" /
    

    Android 8.0以上需求运用TYPE_APPLICATION_OVERLAY的新窗口类型,需求配置SYSTEM_OVERLAY_WINDOW 权限。详细检查Android 8.0窗口改变

  • 翻开权限设置页

    private val launcher = registerForActivityResult(
    		ActivityResultContracts.StartActivityForResult()) { result ->
            if (result.resultCode == Activity.RESULT_OK) {
                showFloatView(x, y)
            }
        }
    private fun checkWindowPermission(): Boolean {
            return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                if (Settings.canDrawOverlays(this.applicationContext)) {
                    true
                } else {
                    val intent = Intent(ACTION_MANAGE_OVERLAY_PERMISSION);
                    intent.data = Uri.parse("package:$packageName")
    		            //翻开悬浮窗权限设置页
                    launcher.launch(intent)
                    false
                }
            } else {
                true
            }
        }
    

b、addView 展现悬浮View

//...省掉...
binding.showFloatView.setOnClickListener {
       if (checkWindowPermission()) {
           showFloatView(0, 0)
       }
}
private fun showFloatView(xo: Int, yo: Int) {
		//1.创立View实例
    val imageView = ImageView(this.applicationContext)
    imageView.setImageResource(R.drawable.icon_joker_doge)
    //2.创立WindowManager.LayoutParams实例
    val lp = WindowManager.LayoutParams().apply {
				width = 250
        height = 250
        type = if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.O) {
            WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
				} else {
            WindowManager.LayoutParams.TYPE_PHONE
				}
        flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or 
								WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
				x = xo
        y = yo
		}
		//3.获取WindowManager实例目标,调用addView办法将视图增加到窗口中
		val windowManager = applicationContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager
		//这儿直接运用Activity的windowManager据说在Android6.0上有问题(关闭界面后不展现?),
    //这儿我没有试过,有低版本手机的同学能够试一下
    imageView.setOnTouchListener(ItemViewTouchListener(lp,windowManager))
		windowManager.addView(imageView, lp)
}

阐明:关于窗口类型type

Android8.0(包含)以上: 有必要新体系窗口类型TYPE_APPLICATION_OVERLAYTYPE_SYSTEM_ALERTTYPE_PHONE 等等类型即便配置弹窗权限也无法展现,会抛出permission denied for window type xxxx反常

Android8.0以下:选一个运用即可,一般用TYPE_PHONE类型:用于供给用户交互操作的非运用窗口;或者TYPE_SYSTEM_ALERT类型:正告类型窗口。

详细检查Android 8.0窗口改变

c、作用

Android 悬浮View实现-WindowManger

d、问题在部分设置界面上无法展现

如下:

Android 悬浮View实现-WindowManger

测试了一下只有在某些体系的设置页面才会被屏蔽,一般情况下上述的弹窗功用就足够了。假如必定要在这些界面也能展现,能够运用TYPE_ACCESSIBILITY_OVERLAY 类型弹窗,需求结合无障碍服务一起运用。

无障碍功用

//context传入无障碍服务的Context
fun showFloatView(context: Context, xo: Int = 0, yo: Int = 0) {
            //1.创立View实例
            val imageView = ImageView(context)
            imageView.setImageResource(R.drawable.icon_joker_doge)
            //2.创立WindowManager.LayoutParams实例
            val lp = WindowManager.LayoutParams().apply {
                width = 250
                height = 250
                //刘海屏延伸到刘海里
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                    layoutInDisplayCutoutMode =
                        WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
                }
                type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY
                } else {
                    WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
                }
                flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                x = xo
                y = yo
            }
            //3.获取WindowManager实例目标,调用addView办法将视图增加到窗口中
            val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
            imageView.setOnTouchListener(ItemViewTouchListener(lp, windowManager))
            windowManager.addView(imageView, lp)
        }
}
//敞开服务,然后展现悬浮窗口,省掉其他代码...
if (checkWindowPermission()) {
    val service = MyAccessibilityService.service
    if (service != null) {
        MyAccessibilityService.showFloatView(service)
       } else {
	         startAccessibilityActivity()
       }
}
//翻开无障碍服务设置界面
private fun startAccessibilityActivity() {
   try {
       val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
       intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
       startActivity(intent)
        } catch (e: Exception) {
       startActivity(Intent(Settings.ACTION_SETTINGS))
       }
    }

作用:

Android 悬浮View实现-WindowManger

能够看到运用TYPE_ACCESSIBILITY_OVERLAY 后能够在之前不能展现的界面展现了

总结:

  • 运用Activity或一般Service+TYPE_APPLICATION_OVERLAY 类型弹窗能够在大部分页面展现悬浮弹窗,能够满足绝大部分需求
  • 假如要在一切界面展现悬浮弹窗,能够运用无障碍Service+TYPE_ACCESSIBILITY_OVERLAY 类型弹窗的办法完成悬浮弹窗

三、Window窗口和android.view.Window类

问题:为什么上面没有创立PhoneWindow目标,终究也能增加一个窗口,PhoneWindow不是Window类的唯一完成吗?

我的了解

WindowManger 管理器的addView 终究会调用ViewRootImpl实例的setView办法

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) {
			res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
											getHostVisibility(), mDisplay.getDisplayId(), userId,
                      mInsetsController.getRequestedVisibilities(), 
                      inputChannel, mTempInsets,mTempControls);
}

mWindowSession 经过IPC跨进程通讯终究会调用WindowManagerServiceaddWindow 办法去显现窗口视图,展现到屏幕上。这儿addWindow就是咱们常说的去新增一个窗口,终究展现开端窗口里面的视图,能够看到咱们其实并没有创立一个真实的android.view.Window类目标。

能够怎么了解:Window 是一个抽象概念,每一个 Window都对应着一个 View 和一个 ViewRootImplWIndowView 经过 ViewRootImpl 建立联系,因而 WindowView 的方式存在,View 才是 Window 存在的实体。这儿说的Window 窗口和android.view.Window类并不是一个东西

那么android.view.Window类以及它的子类PhoneWindow是做什么的?

Abstract base class for a top-level window look and behavior policy. An instance of this class should be used as the top-level view added to the window manager. It provides standard UI policies such as a background, title area, default key processing, etc

官方注释对该类的解说是:尖端窗口外观和行为战略的抽象基类。此类的实例应该用作增加到窗口管理器的尖端视图。它供给规范的 UI 战略,例如布景、标题区域、默认键处理等。它是去管理增加到窗口管理器的尖端视图,和终究咱们展现在屏幕中的窗口不是一个概念。

参阅文章

官方文档:

  • WindowManager
  • WindowManager.LayoutParams

博客:

  • Android悬浮窗完成 运用WindowManager
  • Android悬浮窗等级
  • Window, WindowManager和WindowManagerService之间的关系
  • Android 了解Window和WindowManager
  • Android | 了解 Window 和 WindowManager
  • Android全面解析之Window机制
  • Android悬浮窗看这篇就够了