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

简介

我们在日常开发中,直接接触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及以上系统中,如果要使用悬浮窗,还必须要在系统设置中的“在其它应用上层显示”的权限开关勾选

以下是一个简单的悬浮窗示例代码,可以在Android应用使用: ``` public class FloatingWindowService extends Service { private WindowManager mWindowManager; private View mFloatingView; public FloatingWindowService() { } @Override public IBinder onBind(Intent intent) { return null; } @Override public int onStartCommand(Intent intent, int flags, int startId) { return super.onStartCommand(intent, flags, startId); } @Override public void onCreate() { super.onCreate(); // 初始化悬浮窗视图 mFloatingView = LayoutInflater.from(this).inflate(R.layout.floating_window_layout, null); // 设置悬浮窗参数 WindowManager.LayoutParams params = new WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); // 设置悬浮窗位置 params.gravity = Gravity.TOP | Gravity.LEFT; params.x = 0; params.y = 100; // 添加悬浮窗视图到窗口管理器 mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE); mWindowManager.addView(mFloatingView, params); // 设置悬浮窗视图事件监听 mFloatingView.setOnTouchListener(new View.OnTouchListener() { private int initialX; private int initialY; private float initialTouchX; private float initialTouchY; @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // 记录初始位置和触摸点位置 initialX = params.x; initialY = params.y; initialTouchX = event.getRawX(); initialTouchY = event.getRawY(); return true; case MotionEvent.ACTION_UP: // 如果移动距离很小,则视为单击事件,执行相应操作 if (Math.abs(event.getRawX() - initialTouchX) < 5 && Math.abs(event.getRawY() - initialTouchY) < 5) { // TODO 执行单击事件 } return true; case MotionEvent.ACTION_MOVE: // 计算移动距离并更新悬浮窗位置 params.x = initialX + (int) (event.getRawX() - initialTouchX); params.y = initialY + (int) (event.getRawY() - initialTouchY); mWindowManager.updateViewLayout(mFloatingView, params); return true; } return false; } }); } @Override public void onDestroy() { super.onDestroy(); // 从窗口管理器移除悬浮窗视图 if (mFloatingView != null) { mWindowManager.removeView(mFloatingView); } } } ``` 在上述代码,首先定义了一个`FloatingWindowService`服务类,该类继承自`Service`,并实现了`onCreate()`、`onStartCommand()`和`onDestroy()`方法。在`onCreate()`方法,初始化了悬浮窗视图,并设置了悬浮窗参数和位置,然后将悬浮窗视图添加到窗口管理器。同时,也设置了悬浮窗视图的事件监听,以便实现拖拽和单击操作。 在`onDestroy()`方法,从窗口管理器移除了悬浮窗视图。 需要注意的是,为了在Android 8.0及以上版本显示悬浮窗,需要使用`WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY`类型的窗口。此外,还需要在应用的`AndroidManifest.xml`文件添加`<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />`权限声明。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值