Android基础 悬浮窗

2019-11-18  本文已影响0人  LiuJP

1、浮窗为什么会“浮”?

上面讲到Activity的显示过程其实已经揭示了通用界面的显示过程,浮窗的显示过程更为简单:


image.png image.png

做过浮窗的同学应该都明白了,为啥浮窗能脱离Activity而显示,本质上我们是把一个View交给WindowManager来管理了,LayoutParams.type类型决定了这个View显示窗口的类型,不同类型显示的窗口层次(z轴)是不一样的。大方面来讲可以分为应用窗口(APPLICATION_WINDOW)、子窗口(SUB_WINDOW)、系统窗口(SYSTEM_WINDOW)三种类型,应用窗口z轴范围是1~99,子窗口的范围是1001~1999,系统窗口是(2000~2999),所以要实现浮动窗口我们只能在系统窗口范围中实现。

类型 常量范围 子类 说明 例子
APPLICATION_WINDOW 1~99 TYPE_BASE_APPLICATION 1
TYPE_APPLICATION 2 应用窗口 大部分的应用程序窗口
TYPE_APPLICATION_STARTING 3 应用程序的Activity显示之前由系统显示的窗口
LAST_APPLICATION_WINDOW 99
SUB_WINDOW 1000~1999 FIRST_SUB_WINDOW 1000
TYPE_APPLICATION_PANEL 1000 显示在母窗口之上,遮挡其下面的应用窗口。
TYPE_APPLICATION_MEDIA 1001 显示在母窗口之下,如果应用窗口不挖洞,即不可见。 SurfaceView,在小窗口显示时设为MEDIA, 全屏显示时设为PANEL
TYPE_APPLICATION_SUB_PANEL 1002
TYPE_APPLICATION_ATTACHED_DIALOG 1003
TYPE_APPLICATION_MEIDA_OVERLAY 1004 用于两个SurfaceView的合成,如果设为MEDIA, 则上面的SurfaceView 挡住下面的SurfaceView
LAST_SUB_WINDOW 1999 最后一个子窗口
SYSTEM_WINDOW 2000~2999 TYPE_STATUS_BAR 2000 顶部的状态栏
TYPE_SEARCH_BAR 2001 搜索窗口,系统中只能有一个搜索窗口
TYPE_PHONE 2002 电话窗口
TYPE_SYSTEM_ALERT 2003 警告窗口,在所有其他窗口之上显示 电量不足提醒窗口
TYPE_KEYGUARD 2004 锁屏界面
TYPE_TOAST 2005 短时的文字提醒小窗口
TYPE_SYSTEM_OVERLAY 2006 没有焦点的浮动窗口
TYPE_PRIORITY_PHONE 2007 紧急电话窗口,可以显示在屏保之上
TYPE_SYSTEM_DIALOG 2008 系统信息弹出窗口 比如SIM插上后弹出的运营商信息窗口
TYPE_KEYGUARD_DIALOG 2009 跟KeyGuard绑定的弹出对话框 锁屏时的滑动解锁窗口
TYPE_SYSTEM_ERROR 2010 系统错误提示窗口 ANR 窗口
TYPE_INPUT_METHOD 2011 输入法窗口,会挤占当前应用的空间
TYPE_INPUT_METHOD_DIALOG 2012 弹出的输入法窗口,不会挤占当前应用窗口空间,在其之上显示
TYPE_WALLPAPER 2013 墙纸
TYPE_STATUS_BAR_PANEL 2014 从状态条下拉的窗口
TYPE_SECURE_SYSTEM_OVERLAY 2015 只有系统用户可以创建的OVERLAY窗口
TYPE_DRAG 2016 浮动的可拖动窗口 360安全卫士的浮动精灵
TYPE_STATUS_BAR_PANEL 2017
TYPE_POINTER 2018 光标
TYPE_NAVIGATION_BAR 2019
TYPE_VOLUME_OVERLAY 2020 音量调节窗口
TYPE_BOOT_PROGRESS 2021 启动进度,在所有窗口之上
TYPE_HIDDEN_NAV_CONSUMER 2022 隐藏的导航栏
TYPE_DREAM 2023 屏保动画
TYPE_NAVIGATION_BAR_PANEL 2024 Navigation bar 弹出的窗口 比如说应用收集栏
TYPE_UNIVERSAL_BACKGROUND 2025
TYPE_DISPLAY_OVERLAY 2026 用于模拟第二显示设备
TYPE_MAGNIFICATION 2027 用于放大局部
TYPE_RECENTS_OVERLAY 2028 当前应用窗口,多用户情况下只显示在用户节目

到这里我们对Android系统的窗口层次有个大致的了解了,Activity是Android应用的四大组件之一,描述的是应用的活动状态和周期,受ActivityManagerService的管理;Window/View是图形窗口的抽象模型,描述的是窗口的绘制信息,受WindowManagerService的管理;Activity聚合Window来和图形窗口产生联系。文章旨在理解一下Android窗体系统的一个雏形

2、越过用户授权使用浮窗

2.1类型为TYPE_PHONE、TYPE_PRIORITY_PHONE、TYPE_SYSTEM_ALERT、TYPE_SYSTEM_ERROR、TYPE_SYSTEM_ERROR这些的窗口都是需要用户授权的.
2.2类型为TYPE_TOAST的不需要

2.2.1在Android 4.4 (api 19)以下TYPE_TOAST是无法获取焦点的,所以4.4以下使用TYPE_PHONE就可以,不需要授权;


image.png

2.2.2输入法的限制
在4.4以上使用TYPE_TOAST还是有些小小的限制,如果浮窗交互中需要输入框,TYPE_TOAST和TYPE_PHONE两种类型窗体对输入法的处理还是有些区别。当我们的浮窗在横屏环境中(浮窗下面的应用是横屏的),输入法默认是全屏的,我们可以通过设置文本属性android:imeOptions=“flagNoExtractUi”来禁止输入法的全屏,同时可以设置窗体属性为adjustResize来适配调整浮窗位置防止输入法盖住输入框。

image.png

然而adjustResize这个属性对TYPE_TOAST类型的窗体是无效的,所以如果你的浮窗交互中是需要输入文字的,就不能使用半屏幕输入法的体验了。

image.png

为了最大程度的优化体验,我们使用浮窗的流程可以细化为:


image.png

总结

一般来说,根据type值大小关系,可以推出系统窗口在子窗口的上面,子窗口在应用窗口的上面。
在不使用系统悬浮窗的情况下,使用子窗口是最上层的窗口,WindowManager.LayoutParams.LAST_SUB_WINDOW

FloatManager.java

package com.gameassist.plugin.view;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.graphics.PixelFormat;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;

import com.gameassist.plugin.controller.MainController;
import com.gameassist.plugin.utils.Logger;

public class FloatManager {

    private final Activity activity;
    private WindowManager windowManager;
    private WindowManager.LayoutParams layoutParams;
    private WindowManager.LayoutParams layoutParamsFloat = null;
    private ViewGroup view;
    private Context context;

    public ViewGroup getView() {
        return view;
    }
 

    public FloatManager(Activity activity) {
        this.activity = activity;
        if (layoutParamsFloat == null) {
            layoutParamsFloat = new WindowManager.LayoutParams();
            layoutParamsFloat.gravity = Gravity.CENTER;
            layoutParamsFloat.width = WindowManager.LayoutParams.WRAP_CONTENT;
            layoutParamsFloat.height = WindowManager.LayoutParams.WRAP_CONTENT;
            layoutParamsFloat.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
            layoutParamsFloat.format = PixelFormat.TRANSLUCENT;
            layoutParamsFloat.type = WindowManager.LayoutParams.LAST_SUB_WINDOW;
        }

        if (layoutParams == null) {
            layoutParams = new WindowManager.LayoutParams();
            layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
            layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;
            layoutParams.height = WindowManager.LayoutParams.MATCH_PARENT;
            layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
            layoutParams.format = PixelFormat.TRANSLUCENT;
            layoutParams.type = WindowManager.LayoutParams.LAST_SUB_WINDOW;
        }

    }


    public void showFloat(final View view) {
        try {
            view.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    final int x = (int) event.getX();
                    final int y = (int) event.getY();
                    if ((event.getAction() == MotionEvent.ACTION_DOWN) && ((x < 0) || (x >= v.getWidth()) || (y < 0) || (y >= v.getHeight()))) {
                        windowManager.removeViewImmediate(view);
                    } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
                        windowManager.removeViewImmediate(view);

                    }
                    return false;
                }
            });
            windowManager = activity.getWindowManager();
            if (windowManager != null) {
                if (view.getParent() != null) {
                    ((ViewGroup) view.getParent()).removeView(view);
                }
                windowManager.addView(view, layoutParamsFloat);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public void closeView(View view) {
        windowManager.removeViewImmediate(view);
    }


    @SuppressLint("ClickableViewAccessibility")
    public void showVirtualKey(Context context, final Activity activity, String hash, String emuType) {
        this.context = context;
        view = MainController.getInstance().initView(context, emuType, hash, activity);
        try {
            if (activity != null) {
                Logger.e("view" + view.getParent());
                if (view.getParent() != null) {
                    ((ViewGroup) view.getParent()).removeView(view);
                }
                activity.addContentView(view, layoutParams);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @SuppressLint("ClickableViewAccessibility")
    public void showVirtualKeyFloat(Context context, final Activity activity, String hash, String emuType) {
        this.context = context;
        view = MainController.getInstance().initView(context, emuType, hash, activity);
        try {
            windowManager = activity.getWindowManager();
            if (windowManager != null) {
                if (view.getParent() != null) {
                    ((ViewGroup) view.getParent()).removeView(view);
                }
                Logger.e("view:"+view);
                windowManager.addView(view, layoutParams);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void OnActivityPause(Activity activity) {
        try {
            if (null != windowManager) {
                Logger.e("OnActivityPause:windowManager");
                windowManager.removeViewImmediate(view);
            }
        } catch (Exception e) {
            Logger.e(e.getMessage());
        }
    }


    public void setVisibleChild(String tag, int visible) {
        for (int i = 0; i < view.getChildCount(); i++) {
            View viewi = view.getChildAt(i);
            Object tmp = viewi.getTag();
            if (tmp != null) {
                if (tmp.toString().startsWith(tag)) {
                    viewi.setVisibility(visible);
                }
            }
        }
    }
}
上一篇下一篇

猜你喜欢

热点阅读