这篇文章作为自己自学的总结,主要是借鉴任玉刚的《Android开发艺术探索》。希望大家能喜欢。
前言
Window表示一个窗口的概念,我们在日常开发中直接接触Window的机会并不多,但是在某些特殊时候我们需要在桌面上显示一个类似悬浮窗的东西,那么这种效果就需要用到Window来实现。简单来说就是在桌面显示我们想要的东西,就需要用到Window来实现。
1、Window、WindowManager和WindowManagerService三者简单的关系
Window是一个抽象类,它的具体实现是PhoneWindow。创建一个Window对象只需要通过WindowManager即可实现。WindowManager是外界访问Window的入口,Window的具体实现位于WindowManagerService中,WindowManager和WindowManagerService的交互是一个IPC过程。
需要注意的是:在Android中所有界面都是通过Window来呈现的,不论是Activity、还是Dialog以及Toast,它们都依附在Window上。所以Window实际上是View的直接管理者。
2、掌握如何使用WindowManager添加一个Window
WindowManager windowManager = (WindowManager)getApplicationContext().getSystemService(Context.WINDOW_SERVICE);//获取WindowManager实例
Button button = new Button(this);//实例一个按钮
button.setText("我是测试");//为按钮添加内容
button.setOnClickListener(new View.OnClickListener() {//事件监听也能够实现
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this,"我是测试",Toast.LENGTH_SHORT).show();
}
});
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,0,0, PixelFormat.TRANSPARENT);//获取布局参数
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; //添加flags
layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;//添加type
layoutParams.gravity = Gravity.START | Gravity.TOP; //设置Window的对齐方式
layoutParams.x = 100;//设置Window在桌面显示的x方面的位置
layoutParams.y = 300;//设置Window在桌面显示的y方面的位置
windowManager.addView(button,layoutParams);//最后是将Window进行添加
3、了解掌握WindowManager.LayoutParams中的flags和type两个参数
3.1、flags参数
Flags参数表示Window的属性。我们可以通过设置 Flags的值来控制Window的显示特性。我们这里就只讲解在上文代码中用到的三种比较常用的值。查看官方文档
FLAG_NOT_FOCUSABLE表示Window不需要获取焦点,也不需要接收各种输入事件,此标记会同时启用FLAG_NOT_TOUCH_MODEL,最终事件会直接传递给下层的具有焦点的Window。
FLAG_NOT_TOUCH_MODEL在此模式下,系统会将当前Window区域以外的单击事件传递给底层的Window,当前Window区域以内的单击事件则自己处理。这个标记很重要,一般来首都需要开启此标记,否则其他Window将无法收到单击事件。
例如:我们将上文中的代码FLAG_NOT_FOCUSABLE以及FLAG_NOT_TOUCH_MODEL两个值去掉,会发现我们无论点击何处,点击事件只能被button层的Window所消耗,其它不能接收到事件。
FLAG_SHOW_WHEN_LOCKED开启此模式可以让Window显示在锁屏的界面上。
3.2、type参数
Type参数表示Window的类别。Window有三种类型,分别是应用Window
、子Window
以及系统Window
。Window是分层的,每一个Window都有对应的z-ordered,层级大的会覆盖在层级小的Window的上面。这些层级范围对应着WindowManager.LayoutParams的type参数。
应用类Window
对应着一个Activity。(层级范围1~99)子Window
不能单独存在,它需要附属在特定的父Window之中,例如Dialog和PopWindow。(层级范围1000~1999)系统Window
是需要声明权限才能创建的Window,例如Toast和系统状态栏。(层级范围2000~2999)- 需要注意的是:
通过WindowManager给Window添加控件时,需要给LayoutParams指定type。
//表示系统层级
layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
并且需要在AndroidManifest.xml中申请权限:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
在6.0以上的系统需要自己去实现权限的申请。不然会报的异常
android.view.WindowManager$InvalidDisplayException: Unable to add window android.view.ViewRootImpl$W@1acab724 -- the specified window type is not valid
at android.view.ViewRootImpl.setView(ViewRootImpl.java:712)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:299)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:85)
........
上述代码可以将一个Button添加到屏幕 座标为(100,300)的位置上。
4、了解WindowManager主要的三个方法
查看Window源码可以看到,WindowManager接口是继承ViewManager的,而ViewManager中提供了三种方法让我们可以来操作View,一般我们用这三个功能就ok了。如下
public interface ViewManager
{
//添加View
public void addView(View view, ViewGroup.LayoutParams params);
//刷新View
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
//移除View
public void removeView(View view);
}
我们也可以将我们上面的例子实现拖动效果。代码如下:
WindowManager windowManager;
Button button;
WindowManager.LayoutParams layoutParams;
/**
* 在WindowManager中添加一个Window
*/
private void windowManagerAddWindow() {
windowManager = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);//获取WindowManager实例
button = new Button(this);//实例一个按钮
button.setText("我是测试");//为按钮添加内容
button.setOnTouchListener(this);
layoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);//获取布局参数
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
layoutParams.gravity = Gravity.START | Gravity.TOP;
layoutParams.x = 100;
layoutParams.y = 350;
windowManager.addView(button, layoutParams);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
int rawX = (int) event.getRawX();//相对于屏幕而言
int rawY = (int) event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
layoutParams.x = rawX;
layoutParams.y = rawY;
windowManager.updateViewLayout(button, layoutParams);
break;
default:
break;
}
return false;
}
参考
1、Android 8.0系统上使用WindowManager添加view的一个问题
2、《Android开发艺术探索》第八章