Android基础-悬浮窗

399 阅读6分钟

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_WINDOW1~99TYPE_BASE_APPLICATION1  
  TYPE_APPLICATION2 应用窗口 大部分的应用程序窗口
  TYPE_APPLICATION_STARTING3 应用程序的Activity显示之前由系统显示的窗口 
  LAST_APPLICATION_WINDOW99  
SUB_WINDOW1000~1999FIRST_SUB_WINDOW1000  
  TYPE_APPLICATION_PANEL1000 显示在母窗口之上,遮挡其下面的应用窗口。 
  TYPE_APPLICATION_MEDIA1001 显示在母窗口之下,如果应用窗口不挖洞,即不可见。SurfaceView,在小窗口显示时设为MEDIA, 全屏显示时设为PANEL
  TYPE_APPLICATION_SUB_PANEL1002 
  TYPE_APPLICATION_ATTACHED_DIALOG1003  
  TYPE_APPLICATION_MEIDA_OVERLAY1004 用于两个SurfaceView的合成,如果设为MEDIA, 则上面的SurfaceView 挡住下面的SurfaceView 
  LAST_SUB_WINDOW1999 最后一个子窗口 
SYSTEM_WINDOW2000~2999TYPE_STATUS_BAR2000顶部的状态栏 
  TYPE_SEARCH_BAR2001搜索窗口,系统中只能有一个搜索窗口 
  TYPE_PHONE2002 电话窗口 
  TYPE_SYSTEM_ALERT2003警告窗口,在所有其他窗口之上显示电量不足提醒窗口
  TYPE_KEYGUARD2004锁屏界面 
  TYPE_TOAST2005短时的文字提醒小窗口 
  TYPE_SYSTEM_OVERLAY2006没有焦点的浮动窗口 
  TYPE_PRIORITY_PHONE2007紧急电话窗口,可以显示在屏保之上 
  TYPE_SYSTEM_DIALOG2008系统信息弹出窗口 比如SIM插上后弹出的运营商信息窗口
  TYPE_KEYGUARD_DIALOG2009跟KeyGuard绑定的弹出对话框锁屏时的滑动解锁窗口
  TYPE_SYSTEM_ERROR2010系统错误提示窗口 ANR 窗口
  TYPE_INPUT_METHOD2011输入法窗口,会挤占当前应用的空间 
  TYPE_INPUT_METHOD_DIALOG2012弹出的输入法窗口,不会挤占当前应用窗口空间,在其之上显示 
  TYPE_WALLPAPER2013 墙纸 
  TYPE_STATUS_BAR_PANEL2014从状态条下拉的窗口 
  TYPE_SECURE_SYSTEM_OVERLAY2015只有系统用户可以创建的OVERLAY窗口 
  TYPE_DRAG2016浮动的可拖动窗口360安全卫士的浮动精灵
  TYPE_STATUS_BAR_PANEL2017  
  TYPE_POINTER2018光标 
  TYPE_NAVIGATION_BAR2019  
  TYPE_VOLUME_OVERLAY2020音量调节窗口 
  TYPE_BOOT_PROGRESS2021启动进度,在所有窗口之上 
  TYPE_HIDDEN_NAV_CONSUMER2022隐藏的导航栏 
  TYPE_DREAM2023屏保动画 
  TYPE_NAVIGATION_BAR_PANEL2024Navigation bar 弹出的窗口比如说应用收集栏
  TYPE_UNIVERSAL_BACKGROUND2025  
  TYPE_DISPLAY_OVERLAY2026用于模拟第二显示设备 
  TYPE_MAGNIFICATION2027用于放大局部 
  TYPE_RECENTS_OVERLAY2028当前应用窗口,多用户情况下只显示在用户节目

到这里我们对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);
                }
            }
        }
    }
}