一、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,有两个参数比较重要type
和flags
,下面别离阐明下这两个参数
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
的办法去新增展现视图,因而对咱们运用层开发来讲不论设置运用窗口类型,还是子窗口类型,终究出现的作用是没有区别的。终究结果都是往已经存在的窗口根底上去新增一个窗口,例如运用Activity
的WindowManger
去增加,只能在当前Activity
中展现。 -
特别注意的是一切上述窗口类型需求在
Activity
finish
时分调用removeViewImmediate
办法移除,不然会抛出android.view.WindowLeaked
反常。这和Dialog
和PopWindow
是相同的,需求在父窗口销毁先调用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_OVERLAY
。TYPE_SYSTEM_ALERT
、TYPE_PHONE
等等类型即便配置弹窗权限也无法展现,会抛出permission denied for window type xxxx
反常
Android8.0以下:选一个运用即可,一般用TYPE_PHONE
类型:用于供给用户交互操作的非运用窗口;或者TYPE_SYSTEM_ALERT
类型:正告类型窗口。
详细检查Android 8.0窗口改变
c、作用
d、问题在部分设置界面上无法展现
如下:
测试了一下只有在某些体系的设置页面才会被屏蔽,一般情况下上述的弹窗功用就足够了。假如必定要在这些界面也能展现,能够运用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))
}
}
作用:
能够看到运用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跨进程通讯终究会调用WindowManagerService
的addWindow
办法去显现窗口视图,展现到屏幕上。这儿addWindow就是咱们常说的去新增一个窗口,终究展现开端窗口里面的视图,能够看到咱们其实并没有创立一个真实的android.view.Window
类目标。
能够怎么了解:Window
是一个抽象概念,每一个 Window
都对应着一个 View
和一个 ViewRootImpl
,WIndow
和 View
经过 ViewRootImpl
建立联系,因而 Window
以 View
的方式存在,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悬浮窗看这篇就够了