美文网首页
Android基础 悬浮窗

Android基础 悬浮窗

作者: LiuJP | 来源:发表于2019-11-18 10:41 被阅读0次

    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);
                    }
                }
            }
        }
    }
    

    相关文章

      网友评论

          本文标题:Android基础 悬浮窗

          本文链接:https://www.haomeiwen.com/subject/xfjvictx.html