Android基础 悬浮窗

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);
                }
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值