ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
### 8.1 Window和WindowManager 为了分析Window的工作机制,我们需要先了解如何使用WindowManager添加一个Window。下面的代码演示了通过WindowManager添加Window的过程,是不是很简单呢? mFloatingButton = new Button(this); mFloatingButton.setText("button"); mLayoutParams = new WindowManager.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT); mLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams. FLAG_SHOW_WHEN_LOCKED mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; mLayoutParams.x = 100; mLayoutParams.y = 300; mWindowManager.addView(mFloatingButton, mLayoutParams); 上述代码可以将一个Button添加到屏幕坐标为(100,300)的位置上。WindowManager. LayoutParams中的flags和type这两个参数比较重要,下面对其进行说明。 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显示在锁屏的界面上。 Type参数表示Window的类型,Window有三种类型,分别是应用Window、子Window和系统Window。应用类Window对应着一个Activity。子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,这些层级范围对应着WindowManager.LayoutParams的type参数。如果想要Window位于所有Window的最顶层,那么采用较大的层级即可。很显然系统Window的层级是最大的,而且系统层级有很多值,一般我们可以选用TYPE_SYSTEM_OVERLAY或者TYPE_SYSTEM_ERROR,如果采用TYPE_SYSTEM_ERROR,只需要为type参数指定这个层级即可:`mLayoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR;`同时声明权限:`<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />`。因为系统类型的Window是需要检查权限的,如果不在AndroidManifest中使用相应的权限,那么创建Window的时候就会报错,错误如下所示。 E/AndroidRuntime(8071): Caused by: android.view.WindowManager$BadToken- Exception: Unable to add window android.view.ViewRootImpl$W@42882fe8 -- permission denied for this window type E/AndroidRuntime(8071): at android.view.ViewRootImpl.setView(ViewRootImpl. java:677) E/AndroidRuntime(8071): at android.view.WindowManagerImpl.addView(Window- ManagerImpl.java:326) E/AndroidRuntime(8071): at android.view.WindowManagerImpl.addView(Window- ManagerImpl.java:224) E/AndroidRuntime(8071): at android.view.WindowManagerImpl$CompatMode- Wrapper.addView(WindowManagerImpl.java:149) E/AndroidRuntime(8071): at android.view.Window$LocalWindowManager.addView (Window.java:558) E/AndroidRuntime(8071): at com.ryg.chapter_8.TestActivity.onButtonClick (TestActivity.java:60) E/AndroidRuntime(8071): ... 14 more W/ActivityManager( 514): Force finishing activity com.ryg.chapter_8/. TestActivity WindowManager所提供的功能很简单,常用的只有三个方法,即添加View、更新View和删除View,这三个方法定义在ViewManager中,而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常用的就只有这三个功能而已,但是这三个功能已经足够我们使用了。它可以创建一个Window并向其添加View,还可以更新Window中的View,另外如果想要删除一个Window,那么只需要删除它里面的View即可。由此来看,WindowManager操作Window的过程更像是在操作Window中的View。我们时常见到那种可以拖动的Window效果,其实是很好实现的,只需要根据手指的位置来设定LayoutParams中的x和y的值即可改变Window的位置。首先给View设置onTouchListener:mFloatingButton.setOnTouchListener(this)。然后在onTouch方法中不断更新View的位置即可: public boolean onTouch(View v, MotionEvent event) { int rawX = (int) event.getRawX(); int rawY = (int) event.getRawY(); switch (event.getAction()) { case MotionEvent.ACTION_MOVE: { mLayoutParams.x = rawX; mLayoutParams.y = rawY; mWindowManager.updateViewLayout(mFloatingButton, mLayoutParams); break; } default: break; } return false; }