A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

高原老师

初级黑马

  • 黑马币:

  • 帖子:

  • 精华:

© 高原老师 初级黑马   /  2016-3-2 11:50  /  2209 人查看  /  0 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

本帖最后由 高原老师 于 2016-3-2 11:52 编辑

简介:有时候我们需要显示一个悬浮窗,而这个悬浮窗不只是显示在我们的应用中,还可以显示在别的应用中,比如桌面上的悬浮球,来去电界面的归属地等等,这种效果就需要使用WindowManager来实现。本文就详细地介绍了WindowManager的使用。
1. WindowManagerWindow的介绍
1.1. Window
Window表示一个窗口。对于Android里的Window,我们可以类比Windows系统中的Window,在Windows中,每打开一个软件,都会弹出一个窗口,这个窗口右上角有最小化,最大化,关闭按钮,做了某些操作时,也可能会弹出一个窗口,下面可能会有确定,取消之类的按钮,这些都是Windows系统中的窗口。如图所示:

在Android里,也有Window的概念,但是Android里的Window没有边框, 也没有最大最小关闭按钮的。如图所示:

Android中所有的界面都是显示在一个个Window中的,包括Activity,Dialog,Toast,甚至状态栏,最近应用列表,都是在Window中显示的。只是我们看不到这些Window的边框,只能看到里面的内容。其实 Window并不能真正的显示内容,它只是一个虚拟的"框",真正能显示内容的是View。Window是View的直接管理者,触摸事件也是先由Window接收,然后传递给View的。
1.2. WindowManager
WindowManager是Window的管理者,对应着系统底层的一个服务:WindowManagerService。
我们无法直接访问Window,要操作Window,必须通过WindowManager。WindowManager有三个常用方法:addView,removeView,updateViewLayout我们可以通过WindowManager往屏幕上添加/删除一个Window,或者通过它修改一个Window的布局参数。
2. WindowManager使用详解
2.1. 往屏幕上添加一个Window
调用WindowManager的addView方法即可。
  1. private WindowManager mWindowManager;
  2. private View mView;
  3. private WindowManager.LayoutParams mParams;
  4. private void addWindow() {
  5.     mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
  6.     mView = new TextView(getApplicationContext());
  7.     TextView tv = (TextView) mView;
  8.     tv.setText("我是Window中的View");
  9.     tv.setTextColor(Color.RED);
  10.     mParams = new WindowManager.LayoutParams();
  11.     // 设置宽高
  12.     mParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
  13.     mParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
  14.     // 设置Window的背景支持半透明
  15.     mParams.format = PixelFormat.TRANSLUCENT;
  16.     mParams.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
  17.         | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
  18.         | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
  19.     mParams.type = WindowManager.LayoutParams.TYPE_TOAST;
  20.     // 设置Window的对齐方式,其实就是设置Window的坐标原点位置
  21.     mParams.gravity = Gravity.LEFT | Gravity.TOP;
  22.     // 设置Window的 x,y 坐标(相对于坐标原点)
  23.     mParams.x = 100;
  24.     mParams.y = 250;
  25.     // 设置Window标题,显示在在 HierarchyView 透视图中的 Windows面板里的名称
  26.     mParams.setTitle("AddWindow");
  27.     // 往屏幕上添加一个Window,并且把第一个参数View放在Window中
  28.     mWindowManager.addView(mView,mParams);
  29. }
复制代码
显示效果如下:

2.2. 代码详解
上面的代码表示:调用 WindowManager的 addView(View view,WindowManager。LayoutParams lp) 方法,往屏幕上添加一个Window,这个Window中显示的内容为第一个参数设置的View,Window的显示位置以及其他属性由第二个参数 WindowManager。LayoutParams指定。
这个方法很简单,但是WindowManager。LayoutParams 中有两个字段比较重要,这里详细说一下。
1.flags
用来控制Window的显示特性,有很多可取的值,不同的的值表示不同的显示特性,如果希望Window具有多个值的特性, 可以使用“|” 将这些值进行按位或运算。这里介绍几个比较常用的取值:
  
FLAG_NOT_TOUCHABLE
  
Window不接收触摸事件
FLAG_NOT_FOCUSABLE
Window不获取焦点,即不能接收按键事件,按键事件传递给下层具有焦点的Window
FLAG_NOT_TOUCH_MODAL
表示系统会将当前Window区域外的任何事件传递给底层的Window,当前Window区域内的事件自己处理,一般来说,都需要开启此标记,否则其他Window无法获取事件. 当设置了FLAG_NOT_FOCUSABLE后,此标记也会自动设置
FLAG_KEEP_SCREEN_ON
Window显示期间,保持屏幕高亮
2.type
用来表示Window的类型,Window有三种大的类型,分别是应用Window,子Window和系统Window。Activity的Window就是一种应用Window,Dialog的Window是一种子Window,子Window不能单独存在,必须附属在特定的父Window中,这也就是为什么Dialog的Context必须是Activity。系统Window大都是(不是全部)需要声明权限才能创建,独立应用Window之外,比如Toast,状态栏等等。
Window是分层的,层级大的会覆盖在层级小的之上,三大类Window中,应用Window层级范围是1-99,子Window是1000-1999,系统Window是2000-2999,这就是为什么Dialog显示在Activity之上,而Toast又可以显示在Dialog之上。如图:

type这个字段的取值有很多,不同的值表示不同的层级范围,具体可以看文档,一般来说,如果这个取值为系统Window层级范围内的值,则需要声明权限:
  1. <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
复制代码
另外,type会影响flags的效果,比如,如果type设置为 TYPE_TOAST,则无论怎样设置flags,这个Window都无法接收触摸事件。
2.3. 查看屏幕上的Window
我们再往屏幕上加一个PopupWindow和一个Dialog,当前界面如下:

在Eclipse中,点击菜单 Window - OpenPerspective - Others,选择HierarchyView,打开,选择Windows面板,可以看到当前屏幕中所有的Window:

我们添加的Window在其中显示的标题为AddWindow,另外,我们可以看到还有别的几个Window,比如 PopupWindow,MainActivity,加粗的那一个其实是MainActivity中弹出的Dialog,还能看到 StatusBar(状态栏),RecentsPanel(最近应用列表)等等,这也证明了我们前面说的,Android中所有的界面都是显示在Window中的。
3. 桌面悬浮窗实现思路
3.1. 在桌面上显示Window
如果我们在Activity中使用WindowManager添加Window,当Activity退出时,添加的Window也会被回收掉。所以要想在桌面上显示悬浮窗,可以在Service中使用WindowManager添加Window,这样只要服务不停止,就可以一直显示。当服务启动时,在其onCreate方法中,使用WindowManager的 addView方法添加一个系统Window,当服务销毁时,可以在其 onDestroy中使用WindowManager的removeView 方法移除Window。大体是这样的思路,代码就不再给出了。
3.2.. 让这个Window随手指移动
要想让这个Window能接收事件,需要给他设置相应的flags(只要不包含FLAG_NOT_TOUCHABLE即可),另外其type也不能是 TYPE_TOAST。可以使用:TYPE_PRIORITY_PHONE,表示比来去电界面的Window级别还要高一些(来去电界面的Window是系统Window)。
  1. mParams.type = WindowManager.LayoutParams. TYPE_PRIORITY_PHONE;
  2. mParams.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
  3.             | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
复制代码
注意添加权限:
  1. <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
复制代码
然后给Window里的View设置onTouchListener,重写onTouch方法:
  1. private int mStartX;
  2. private int mStartY;
  3. @Override
  4. public boolean onTouch(View v,MotionEvent event) {
  5.     switch (event.getAction()) {
  6.     case MotionEvent.ACTION_DOWN:
  7.         // 记录坐标起始点,getRawX,getRawY返回值为float,
  8.         // 需转化为int,变成像素数后再使用
  9.         mStartX = (int) event.getRawX();
  10.         mStartY = (int) event.getRawY();
  11.         break;
  12.     case MotionEvent.ACTION_MOVE:
  13.         int newX = (int) event.getRawX();
  14.         int newY = (int) event.getRawY();
  15.         // 获取手指移动的距离
  16.         int dx = newX - mStartX;
  17.         int dy = newY - mStartY;
  18.         // 修改Window的x,y坐标
  19.         mParams.x += dx;
  20.         mParams.y += dy;
  21.         // 修改Window的布局参数
  22.         // 这里不能修改Window里的View的布局参数,因为View是在Window中显示的,
  23.         // 修改View的布局参数并不能移动外面的Window
  24.         mWindowManager.updateViewLayout(mView,mParams);
  25.         // 重新记录新的坐标起始点
  26.         mStartX = (int) event.getRawX();
  27.         mStartY = (int) event.getRawY();
  28.         break;
  29.     default:
  30.         break;
  31.     }
  32.     return true;
  33. }
复制代码
这样就实现了Window随着手指拖动而移动了。

0 个回复

您需要登录后才可以回帖 登录 | 加入黑马