认识Android中Window(一) 之 悬浮窗的使用

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lyz_zyx/article/details/80868930

简介

我们在日常开发中,直接接触Window的机会并不多,充其量就是在使用悬浮窗这事情上。其实,Android中所有的视图呈现都是通过Window做到的,也就是说,除了悬浮窗外,像Activity、Dialog、Toast都是通过Window来呈现的。所以可以说,Window是View的直接管理者。要创建Window就要通过WindowManager类,它是外界访问Window的入口。今天我们就来通过创建一个悬浮窗的Demo来了解Window和WindowManager。

悬浮窗Demo

public class MainActivity extends Activity {

    private WindowManager mWindowManager;
    private WindowManager.LayoutParams mParams;
    private View mFloatView;

    private float mRawX;
    private float mRawY;
    private float mStartX;
    private float mStartY;
    private int mTitleHeight;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 6.0及以上系统,先跳至系统权限页开启"在其它应用上层显示"权限
        if (Build.VERSION.SDK_INT >= 23) {
            if(!Settings.canDrawOverlays(this)) {
                Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivityForResult(intent, 1);

                return;
            }

        }

        initView();
        initfloat();
    }

    private int getTitleHeight() {
        Rect frame = new Rect();
        getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
        int statusBarHeight = frame.top;
        return statusBarHeight;
    }

    private void updateFloatWndPosition() {
        mParams.x = (int)(mRawX - mStartX);
        mParams.y = (int)(mRawY - mStartY);
        mParams.gravity = Gravity.LEFT | Gravity. TOP;
        mWindowManager.updateViewLayout(mFloatView, mParams);
    }

    private void initView() {
        LayoutInflater lif = (LayoutInflater) (MainActivity.this).getSystemService(LAYOUT_INFLATER_SERVICE);
        mFloatView = lif.inflate(R.layout.float_view, null);
        mFloatView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (mTitleHeight == 0) {
                    mTitleHeight = getTitleHeight();
                }

                mRawX = event.getRawX();
                mRawY = event.getRawY() - mTitleHeight;

                final int action = event.getAction();

                switch (action) {
                    case MotionEvent.ACTION_DOWN:
                        mStartX = event.getX();
                        mStartY = event.getY();

                        break;
                    case MotionEvent.ACTION_MOVE:
                        updateFloatWndPosition();
                        break;
                    case MotionEvent.ACTION_UP:
                        updateFloatWndPosition();
                        break;
                }
                return true;
            }
        });
    }

    private void initfloat() {

        if (mWindowManager != null) {
            return;
        }
        mWindowManager = (WindowManager)getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
        mParams = new WindowManager.LayoutParams();
        mParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        //  WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY
        //  WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
        //  WindowManager.LayoutParams.TYPE_TOAST;
        mParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        mParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
        mParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
        mParams.format = PixelFormat.RGBA_8888;         // PixelFormat.TRANSLUCENT 半透明
        mParams.gravity = Gravity.CENTER;               // Gravity.LEFT | Gravity. TOP;
        // mParams.x = 100;
        // mParams.y = 100;

        mWindowManager.addView(mFloatView, mParams);
    }
}

Window的类型

Window有三种类型,分别是:

应用Window                    对应着一个Activity(层级范围是1~99)

子Window                       不能单独存在,它需要附属在特定的父Window之中,比如Dialog(层级范围是1000~1999)

系统Window                    系统Window是需要声明权限的,比如Toast和系统状态栏这些(范围是2000~2999)

应用Window包含以下几类:

定义

意义

FIRST_APPLICATION_WINDOW = 1

第一个普通应用窗口

TYPE_BASE_APPLICATION = 1

基窗口,所有其他类型的应用窗口将出现在基窗口上层

TYPE_APPLICATION = 2

普通应用窗口

TYPE_APPLICATION_STARTING = 3

应用程序启动时先显示此窗口,当真正的窗口配置完成后,此窗口被关闭

LAST_APPLICATION_WINDOW = 99

最后一个应用窗口

所有Activity默认的窗口类型都是TYPE_APPLICATION,在进行窗口叠加时,会动态改变应用窗口的层值,但层值不会大于99。

子Window包含以下几类:

定义

意义

FIRST_SUB_WINDOW = 1000

第一个子窗口

TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW

应用窗口的子窗口,PopupWindow的默认类型

TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1

用来显示Media的窗口

TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2

TYPE_APPLICATION_PANEL的子窗口

TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3

OptionMenu、ContextMenu的默认类型

TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW + 4

TYPE_APPLICATION_MEDIA的重影窗口,显示在TYPE_APPLICATION_MEDIA和应用窗口之间

LAST_SUB_WINDOW = 1999

最后一个子窗口

创建子窗口时,客户端可以指定窗口类型介于1000-1999之间,在进行窗口叠加时,会动态调整层值。

系统Window有以下类型:

定义

意义

FIRST_SYSTEM_WINDOW = 2000

第一个系统窗口

TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW

状态栏窗口

TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW +1

搜索条窗口

TYPE_PHONE = FIRST_SYSTEM_WINDOW + 2

来电显示窗口

TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW + 3

警告对话框

TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW + 4

屏保

TYPE_TOAST = FIRST_SYSTEM_WINDOW + 5

Toast对应的窗口

TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW + 6

系统覆盖窗口,需要显示在所有窗口之上

TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW + 7

在屏幕保护下的来电显示窗口

TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW + 8

滑动状态条后出现的窗口

TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW + 9

屏保弹出的对话框

TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW + 10

系统错误窗口

TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW + 11

输入法窗口

TYPE_INPUT_METHOD_DIALOG = FIRST_SYSTEM_WINDOW + 12

输入法中备选框对应的窗口

TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW + 13

墙纸对应的窗口

TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW + 14

滑动状态条后出现的窗口

TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW + 15

安全系统覆盖窗口,显示在所有窗口之上。

……

……

TYPE_APPLICATION_OVERLAY FIRST_SYSTEM_WINDOW + 38

8.0新增,系统正式统一开发者使用悬浮窗的类型

LAST_SYSTEM_WINDOW = 2999

最后一个系统窗口

 

Window的属性

Window的属性,常用的选项:

FLAG_NOT_FOCUSABLE

Window不需要获取焦点,也不需要接收各种输入事件(收不到Back键的事件),此标记会同时启用FLAG_NOT_TOUCH_MODEAL,最终事件会直接传递给下层的具有焦点的Window。

FLAG_NOT_TOUCH_MODEAL

收不到触屏事件,不会拦截其他Window的单击事件,一般情况下都需要开启

FLAG_SHOW_WHEN_LOCKED

可让Window显示在锁屏的界面上

版本变动

在Android众多版本的迭代中,其实悬浮窗也有比较多的变化,所以我们在使用中还要针对情况来进行相应的适配。在5.1之前,大多数手机系统里只有声明了<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>权限有就可以在app中弹出悬浮窗,除了国内部分厂商手机如:小米、魅族等定制系统中处理了悬浮窗权限要开关。我们日常开发中,比较常用到的类型有:

WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY

没有实体,不会响应事件,无法进行交互,但可悬浮在锁屏面板

WindowManager.LayoutParams.TYPE_SYSTEM_ALERT

可以接收事件

WindowManager.LayoutParams.TYPE_TOAST

4.4以下无法接收事件,在4.47.0以下可以不加权限声明也能弹出悬浮窗在应用之上,在7.0及以上系统填补了不加权限声明的漏洞,在8.0中不能常显示,像正常Toast一样有显示的时间限制

WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY

8.0上指定开发者使用悬浮窗唯一的类型,其它类型都会报崩溃


6.0及以上系统中,如果要使用悬浮窗,还必须要在系统设置中的“在其它应用上层显示”的权限开关勾选

猜你喜欢

转载自blog.csdn.net/lyz_zyx/article/details/80868930