Android学习之理解Window和WindowManager(一)

这篇文章作为自己自学的总结,主要是借鉴任玉刚的《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开发艺术探索》第八章

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章