1. Window和WindowManager

Window有三种类型,层级大的会覆盖在层级小的上面

  • 应用Window,对应Activity,层级范围是 1~99
  • Window,对应DialogPopupWindow,层级范围是 1000~1999
  • 系统Window,对应Toast层级范围是 2000~2999

WindowManager继承ViewManager,可以显示类似悬浮窗效果,主要实现下面三个方法

public interface ViewManager {
    public void addView(View view, ViewGroup.LayoutParams params); 
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

2. 创建悬浮窗

悬浮窗对应系统Window,通过WindowManager.addView(View, ViewGroup.LayoutParams)加入界面

WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
lp.width = ViewGroup.LayoutParams.WRAP_CONTENT;
lp.gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL; // 窗口位置
lp.format = PixelFormat.TRANSPARENT; // 位图格式
lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; // 窗口的层级关系
lp.flags= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; // 窗口的模式
wm.addView(view, lp);

WindowManager.LayoutParamstype属性取值

常量

含义

FIRST_APPLICATION_WINDOW

应用程序窗口

TYPE_BASE_APPLICATION

所有程序窗口的“基地”窗口

TYPE_APPLICATION

普通应用功能程序窗口

TYPE_APPLICATION_STARTING

用于应用程序启动时所显示的窗口

LAST_APPLICATION_WINDOW

应用程序窗口结束

FIRST_SUB_WINDOW

子窗口

TYPE_APPLICATION_PANE

面板窗口

TYPE_APPLICATION_MEDIA

媒体窗口

TYPE_APPLICATION_SUB_PANEL

应用程序窗口的子面板

TYPE_APPLICATION_ATTACHED_DIALOG

对话框

TYPE_APPLICATION_MEDIA_OVERLAY

媒体信息

LAST_SUB_WINDOW

子窗口结束

FIRST_SYSTEM_WINDOW

系统窗口

TYPE_STATUS_BAR

状态栏

TYPE_SEARCH_BAR

搜索栏

TYPE_PHONE

电话窗口

TYPE_SYSTEM_ALERT

系统提示

TYPE_KEYGUARD

锁屏窗口

TYPE_TOAST

信息窗口

TYPE_SYSTEM_OVERLAY

系统顶层窗口

TYPE_PRIORITY_PHONE

电话优先,当锁屏时显示

TYPE_SYSTEM_DIALOG

系统对话框(例如音量调节框)

TYPE_KEYGUARD_DIALOG

锁屏时显示的对话框

TYPE_SYSTEM_ERROR

系统内部错误提示,显示于所有内容之上

TYPE_INPUT_METHOD

内部输入法窗口,显示于普通UI之上

TYPE_INPUT_METHOD_DIALOG

内部输入法对话框,显示于当前输入法窗口之上

TYPE_WALLPAPER

墙纸窗口

TYPE_STATUS_BAR_PANEL

状态栏的滑动面板

LAST_SYSTEM_WINDOW

系统窗口结束

WindowManager.LayoutParamsflags属性取值

常量

含义

FLAG_ALLOW_LOCK_WHILE_SCREEN_ON

允许在屏幕开启的时候锁定屏幕

FLAG_DIM_BEHIND

window会变暗,使用dimAmount属性来控制变暗的程度

FLAG_NOT_FOCUSABLE

window永远不会获取焦点

FLAG_NOT_TOUCHABLE

window永远无法获取点击事件

FLAG_NOT_TOUCH_MODAL

允许window之外点击事件传递给其他在其之后的window

FLAG_TOUCHABLE_WHEN_WAKING

device休眠的时候,当触摸屏被点击,window会收到首次点击事件

FLAG_KEEP_SCREEN_ON

当这个window对用户是可见状态,保持设备屏幕不关闭且不变暗

FLAG_LAYOUT_IN_SCREEN

将window放置在整个屏幕之内,无视其他的装饰

FLAG_LAYOUT_NO_LIMITS

允许window扩展至屏幕之外

FLAG_FULLSCREEN

当这个window显示的时候,隐藏所有的装饰物

FLAG_FORCE_NOT_FULLSCREEN

覆盖FLAG_FULLSCREEN效果,并强制显示屏幕上的一些装饰

FLAG_SECURE

把这个window中的内容看作需要保护的内容,防止被截屏

FLAG_SCALED

一种特殊模式,布局参数用于指示显示比例

FLAG_IGNORE_CHEEK_PRESSES

会过滤不需要的点击事件

FLAG_LAYOUT_INSET_DECOR

这个flag只能配合 FLAG_LAYOUT_IN_SCREEN 一起使用

FLAG_ALT_FOCUSABLE_IM

反转FLAG_NOT_FOCUSABLE选项

FLAG_WATCH_OUTSIDE_TOUCH

一个点击事件如果发生在你的window之外的范围,你就会接收到一个特殊的MotionEvent,MotionEvent.ACTION_OUTSIDE

FLAG_SHOW_WHEN_LOCKED

使得window可以在锁屏状态下显示

FLAG_SHOW_WALLPAPER

如果你的window有透明的区域,墙纸会显示在那

FLAG_TURN_SCREEN_ON

当window被添加或者显示,系统会点亮屏幕

FLAG_DISMISS_KEYGUARD

当使用的是无密码的锁屏界面,显示此window会使锁屏界面被自动解锁

FLAG_SPLIT_TOUCH

window会接收来自window边界之外发送给其他window的点击事件

FLAG_HARDWARE_ACCELERATED

window是否启动硬件加速,请求硬件加速但不能保证硬件加速生效

FLAG_LOCAL_FOCUS_MODE

在这种模式下,window不会通过windowmanager获取到event

FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS

window负责绘制状态栏的背景

创建悬浮窗需要系统权限

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

3. 悬浮窗禁用

在Android6.0以后,悬浮窗权限默认是禁用的,需要用户确定打开,在返回时确认权限。

public void requestPermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
            && !Settings.canDrawOverlays(this)) {
        Toast.makeText(this, "请授权!", Toast.LENGTH_SHORT).show();
        Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                Uri.parse("package:" + getPackageName()));
        startActivityForResult(intent, REQUEST_OVERLAY_PERMISSION);
    } else {
        showSuspensionWindow(mSuspensionView);
    }
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_OVERLAY_PERMISSION) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Settings.canDrawOverlays(this)) {
            showSuspensionWindow(mSuspensionView);
        }
    }
}