Android WindowManager & Window属性 & 添加悬浮窗的示例代码

#.WindowManager

    Android中用来管理Window的接口类,具体实现类是WindowManagerImpl,而最终是交给WindowManagerService来真实实现相应功能。
##.基本API:

1.addView(View, WindowManager.LayoutParams);

添加Window,并把View作为Window的内容,WindowManager.LayoutParams配置了窗口的参数信息。

2.updateViewLayout(View, WindowManager.LayoutParams);

更新指定Window的配置参数

3.removeView(View);

移除View对应的Window。

#.Window的属性

Window的属性定义在WindowManager.LayoutParams类中。相关属性有很多种,与应用开发最密切的有四类,分别是 Type(Window的类型)、 Flag(Window的标志)、 SoftInputMode(软键盘相关模式)、位置大小相关属性。

##.Window的类型

对应WindowManager.LayoutParams.type,Window的类型同时会决定Window的显示层级Z-Order。
Window的类型有很多种,但整体分为三大类:
1)ApplicationWindow(应用程序窗口):一般位于最底层,Z-Order在1-99
2)Sub Window(子窗口):子窗口必须依附于特定的父Window,Z-Order在1000-1999
3)SystemWindow(系统窗口): 系统级窗口一般位于最顶层,不会被其他的window遮住(如Toast),Z-Order在2000-2999。如果要弹出自定义系统级窗口需要动态申请权限。

1.应用程序窗口

典型的应用程序窗口就是Activity中的窗口。 类型如下:
// 应用程序 Window 的开始值
public static final int FIRST_APPLICATION_WINDOW = 1;
// 应用程序 Window 的基础值
public static final int TYPE_BASE_APPLICATION = 1;
// 普通的应用程序
public static final int TYPE_APPLICATION = 2;
// 特殊的应用程序窗口,当程序可以显示 Window 之前使用这个 Window 来显示一些东西
public static final int TYPE_APPLICATION_STARTING = 3;
// TYPE_APPLICATION 的变体,在应用程序显示之前,WindowManager 会等待这个 Window 绘制完毕
public static final int TYPE_DRAWN_APPLICATION = 4;
// 应用程序 Window 的结束值
public static final int LAST_APPLICATION_WINDOW = 99;

注意:Activity和Dialog中的Window的类型都是TYPE_APPLICATION,Dialog不是子窗口,但Dialog必须附属于某个Activity,用这个Activity作为Context来创建。

2.子窗口

子窗口不能独立存在,必须依附于特定的父Window才行,例如PopupWindow中的Window(类型 TYPE_APPLICATION_PANEL)就属于子窗口,必须依附于一个Activity。子窗口的 类型如下:
// 子 Window 类型的开始值
public static final int FIRST_SUB_WINDOW = 1000;
// 应用程序 Window 顶部的面板。这些 Window 出现在其附加 Window 的顶部。
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
// 用于显示媒体(如视频)的 Window。这些 Window 出现在其附加 Window 的后面。
public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;
// 应用程序 Window 顶部的子面板。这些 Window 出现在其附加 Window 和任何Window的顶部
public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;
// 当前Window的布局和顶级Window布局相同时,不能作为子代的容器
public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;
// 用显示媒体 Window 覆盖顶部的 Window, 这是系统隐藏的 API
public static final int TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW + 4;
// 子面板在应用程序Window的顶部,这些Window显示在其附加Window的顶部, 这是系统隐藏的 API
public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;
// 子 Window 类型的结束值
public static final int LAST_SUB_WINDOW = 1999;

3.系统窗口

例如Toast、输入法窗口、系统音量条窗口、系统错误窗口等都属于系统窗口,显示层级较高,会显示在应用窗口和子窗口的上层。类型如下:
// 系统Window类型的开始值
public static final int FIRST_SYSTEM_WINDOW = 2000;
// 系统状态栏,只能有一个状态栏,它被放置在屏幕的顶部,所有其他窗口都向下移动
public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;
// 系统搜索窗口,只能有一个搜索栏,它被放置在屏幕的顶部
public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1;
// Android8.0以前,添加悬浮窗时,一般会选该类型
public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2;
// Android8.0以后,添加悬浮窗时,一般会选该类型
public static final int TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6;
// 系统对话框窗口
public static final int TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8;
// 锁屏时显示的对话框
public static final int TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9;
// 输入法窗口,位于普通 UI 之上,应用程序可重新布局以免被此窗口覆盖
public static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11;
// 输入法对话框,显示于当前输入法窗口之上
public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;
// 墙纸
public static final int TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW+13;
// 状态栏的滑动面板
public static final int TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+14;
// 应用程序叠加窗口显示在所有窗口之上
public static final int TYPE_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 38;
// 系统Window类型的结束值
public static final int LAST_SYSTEM_WINDOW = 2999;

##.Window的Flag

Window的是Flag用于控制Window的显示特点,常见的Flag有:
// 当 Window 可见时允许锁屏
public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001;
// Window 后面的内容都变暗
public static final int FLAG_DIM_BEHIND = 0x00000002;
// Window 不能获得输入焦点,即不接受任何按键或按钮事件,例如该 Window 上 有 EditView,点击 EditView 是 不会弹出软键盘的
// Window 范围外的事件依旧为原窗口处理;例如点击该窗口外的view,依然会有响应。另外只要设置了此Flag,都将会启用FLAG_NOT_TOUCH_MODAL
public static final int FLAG_NOT_FOCUSABLE = 0x00000008;
// 设置了该 Flag,将 Window 之外的按键事件发送给后面的 Window 处理, 而自己只会处理 Window 区域内的触摸事件
// Window 之外的 view 也是可以响应 touch 事件。
public static final int FLAG_NOT_TOUCH_MODAL  = 0x00000020;
// 设置了该Flag,表示该 Window 将不会接受任何 touch 事件,例如点击该 Window 不会有响应,只会传给下面有聚焦的窗口。
public static final int FLAG_NOT_TOUCHABLE      = 0x00000010;
// 只要 Window 可见时屏幕就会一直亮着
public static final int FLAG_KEEP_SCREEN_ON     = 0x00000080;
// 允许 Window 占满整个屏幕
public static final int FLAG_LAYOUT_IN_SCREEN   = 0x00000100;
// 允许 Window 超过屏幕之外
public static final int FLAG_LAYOUT_NO_LIMITS   = 0x00000200;
// 全屏显示,隐藏所有的 Window 装饰,比如在游戏、播放器中的全屏显示
public static final int FLAG_FULLSCREEN      = 0x00000400;
// 表示比FLAG_FULLSCREEN低一级,会显示状态栏
public static final int FLAG_FORCE_NOT_FULLSCREEN   = 0x00000800;
// 当用户的脸贴近屏幕时(比如打电话),不会去响应此事件
public static final int FLAG_IGNORE_CHEEK_PRESSES    = 0x00008000;
// 则当按键动作发生在 Window 之外时,将接收到一个MotionEvent.ACTION_OUTSIDE事件。
public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000;
@Deprecated
// 窗口可以在锁屏的 Window 之上显示, 使用 Activity#setShowWhenLocked(boolean) 方法代替
public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
// 表示负责绘制系统栏背景。如果设置,系统栏将以透明背景绘制,
// 此 Window 中的相应区域将填充 Window#getStatusBarColor()和 Window#getNavigationBarColor()中指定的颜色。
public static final int FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS = 0x80000000;
// 表示要求系统壁纸显示在该 Window 后面,Window 表面必须是半透明的,才能真正看到它背后的壁纸
public static final int FLAG_SHOW_WALLPAPER = 0x00100000;
设置方式:
//方法1
先设置给WindowManager.LayoutParms
然后在WindowManager.add(View, WindowManager.LayoutParms)添加Window时设置给Window
//方式2
对于已经创建的Window,通过Window的addFlags(int flags)

##.软键盘相关模式

控制窗口和软键盘的显示关系,使软键盘窗口按照期望来显示。常见类型有:
// 没有指定状态,系统会选择一个合适的状态或者依赖于主题的配置
public static final int SOFT_INPUT_STATE_UNCHANGED = 1;
// 当用户进入该窗口时,隐藏软键盘
public static final int SOFT_INPUT_STATE_HIDDEN = 2;
// 当窗口获取焦点时,隐藏软键盘
public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3;
// 当用户进入窗口时,显示软键盘
public static final int SOFT_INPUT_STATE_VISIBLE = 4;
// 当窗口获取焦点时,显示软键盘
public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5;
// window会调整大小以适应软键盘窗口
public static final int SOFT_INPUT_MASK_ADJUST = 0xf0;
// 没有指定状态,系统会选择一个合适的状态或依赖于主题的设置
public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;
// 当软键盘弹出时,窗口会调整大小,例如点击一个EditView,整个layout都将平移可见且处于软件盘的上方
// 同样的该模式不能与SOFT_INPUT_ADJUST_PAN结合使用;
// 如果窗口的布局参数标志包含FLAG_FULLSCREEN,则将忽略这个值,窗口不会调整大小,但会保持全屏。
public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;
// 当软键盘弹出时,窗口不需要调整大小, 要确保输入焦点是可见的,
// 例如有两个EditView的输入框,一个为Ev1,一个为Ev2,当你点击Ev1想要输入数据时,当前的Ev1的输入框会移到软键盘上方
// 该模式不能与SOFT_INPUT_ADJUST_RESIZE结合使用
public static final int SOFT_INPUT_ADJUST_PAN = 0x20;
// 将不会调整大小,直接覆盖在window上
public static final int SOFT_INPUT_ADJUST_NOTHING = 0x30;
设置方法:
//方法1:java代码设置
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
//方法2:在AndroidManifest.xml中指定,如下:
<activity android:windowSoftInputMode="adjustPan" />

4.位置相关的属性

x、y:指定window的左上角在屏幕上的坐标
width/height:Window的宽/高
gravity:指定window的左上角在屏幕上的gravity
alpha:window的透明度
设置的代码示例:
Window window = dialog.getWindow();
window.setGravity(Gravity.CENTER);
DisplayMetrics dm = getContext().getResources().getDisplayMetrics();
WindowManager.LayoutParams lp = window.getAttributes();
lp.width = (int)(dm.widthPixels*0.8);
lp.height = (int)(dm.heightPixels*0.8);
window.setAttributes(lp);

#.添加悬浮窗的示例代码

悬浮窗是通过WindowManager添加Window窗口来实现的,添加窗口类型的是系统窗口,显示层级较高,所以会“浮”在其它窗口上面。要注意相关权限的申请。
<!--    悬浮窗权限   -->
<uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
<!--    Android7.0以上悬浮窗需要-->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
##.简单代码示例
private int mWindowFlag =
            //不拦截触摸事件
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
            //保持常亮
            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
            //在整个屏幕, 不管状态栏
            WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR |
            //在窗口外可以处理触摸, 且监听窗口外的点击
            WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |  WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;

    private void init(Context context){
        mContext = context;
        mWindowManager = (WindowManager) context.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
        //Android8.0之前和之后的窗口类型参数不同
        int mWindowType;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        } else {
            mWindowType = WindowManager.LayoutParams.TYPE_PHONE;
        }
        //设置窗口参数
        mWindowLp = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                mWindowType,
                mWindowFlag,
                PixelFormat.TRANSLUCENT);
        mWindowLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
        //窗口显示位置
        mWindowLp.gravity = Gravity.LEFT | Gravity.TOP;
    }

    /***************      相关控制方法      *****************/
    /**
     * 显示悬浮窗
     */
    public void show() {
        try {
            mWindowManager.addView(this, mWindowLp);
        } catch (Throwable e) {
            ILog.e(TAG, "show", e);
            hide();
        }
    }

    /**
     * 隐藏悬浮窗
     */
    public void hide() {
        if (null != mWindowManager) {
            try {
                mWindowManager.removeView(this);
            } catch (Exception e) {
                ILog.e(TAG, "hide", e);
            }
        }
    }

    /**
     * 更新悬浮窗窗口显示配置:位置等参数
     */
    protected void updateViewLayout() {
        try {
            mWindowManager.updateViewLayout(this, mWindowLp);
        } catch (Throwable e) {
            ILog.e(TAG, "updateViewLayout", e);
        }
    }

猜你喜欢

转载自blog.csdn.net/u013914309/article/details/124806275