Window是一个抽象类,表示一个窗口,他的具体实现类是PhoneWindow。创建一个Window是很简单的事,只需要通过WindowManager即可完成。WindowManager是外界访问Window的入口,Window的具体实现位于WindowManagerService中,WindowManager与WindowManagerService的交互是一个IPC过程。Android中所有的试图都是通过Window来呈现的,不管Activity、Dialog还是Toast,他们实际上都是附加在Window上的,因此,Window是View的直接管理者。
Window分类
Window 有三种类型,分别是应用 Window、子 Window 和系统 Window。应用类 Window 对应一个 Acitivity,子 Window 不能单独存在,需要依附在特定的父 Window 中,比如常见的一些 Dialog 就是一个子 Window。系统 Window是需要声明权限才能创建的 Window,比如 Toast 和系统状态栏都是系统 Window。
Window 是分层的,每个 Window 都有对应的 z-ordered,层级大的会覆盖在层级小的 Window 上面,这和 HTML 中的 z-index 概念是完全一致的。在三种 Window 中,应用 Window 层级范围是 1~99,子 Window 层级范围是 1000~1999,系统 Window 层级范围是 2000~2999,我们可以用一个表格来直观的表示:
Window | 层级 |
---|---|
应用 Window | 1~99 |
子 Window | 1000~1999 |
系统 Window | 2000~2999 |
这些层级范围对应着 WindowManager.LayoutParams 的 type 参数,如果想要 Window 位于所有 Window 的最顶层,那么采用较大的层级即可,很显然系统 Window 的层级是最大的,当我们采用系统层级时,需要声明权限。
WindowManager使用
我们对 Window 的操作是通过 WindowManager 来完成的,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);
}
这三个方法其实就是 WindowManager 对外提供的主要功能,即添加 View、更新 View 和删除 View。接下来来看一个通过 WindowManager 添加 Window 的例子,代码如下:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Button floatingButton = new Button(this);
floatingButton.setText("button");
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
0, 0,
PixelFormat.TRANSPARENT
);
// flag 设置 Window 属性
layoutParams.flags= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
// type 设置 Window 类别(层级)
layoutParams.type = WindowManager.LayoutParams.TYPE_TOAST;
layoutParams.gravity = Gravity.CENTER;
WindowManager windowManager = getWindowManager();
windowManager.addView(floatingButton, layoutParams);
}
}
代码中并没有调用 Activity 的 setContentView 方法,而是直接通过 WindowManager 添加 Window,其中设置为系统 Window,所以应该添加权限:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
Flags表示Window的属性,它有很多选项,通过这些属性可以控制Window的显示属性。
FLAG_NOT_FOCUSABLE
表示window不会获取焦点,也不会接受任何输入事件,此属性会同时启用FLAG_NOT_TOUCH_MODAL,最终事件会直接传递给下层具有焦点的Window。
FLAG_NOT_TOUCH_MODAL
表示在当前Window以外的点击事件会传递给底层Window,以内的则由当前window处理。这个标记很重要,一般来说都需要开启,否则其他Window将无法收到点击事件。
FLAG_SHOW_WHEN_LOCKED
开启此模式可以让window显示在锁屏界面上。比如QQ的消息
注意:如果设置
WindowManager.LayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
在level 19以上的版本就会报下面的error:
android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRootImpl$W@40ec8528 -- permission denied for this window type
所以,一般会用LayoutParams.TYPE_TOAST or TYPE_APPLICATION_PANEL。
效果如下: