如何处理Android悬浮弹窗双击返回事件?

目录

1 前言

1.1 准备知识

1.2 问题概述

2 解决方案

3 代码部分

3.1 动态更新窗口焦点

3.2 窗口监听返回事件

3.3 判断焦点是否在窗口内部

3.4 窗口监听焦点移入/移出


1 前言

1.1 准备知识

1)开发环境

  • 2D开发环境:所有界面或弹窗都在主界面显示;
  • 3D开发环境:保留原生Android的主界面,在主界面之外绘制各种窗口,配合3D渲染以实现3D效果。

2)焦点:就是Hover点、中央注视点、可与用户交互的点。

3)窗口:就是系统弹窗,内部有addView,本文窗口监听即View监听。

4)事件分发:正常Android设备使用如下3种,本文采用的第3种setOnHoverListener获取事件。

  • setOnTouchListener(MotionEvent::InputEvent):手机、平板、车载等屏幕可触控的2D设备;
  • setOnKeyListener(KeyEvent::InputEvent):电视、投影仪等屏幕不可触控的2D设备;
  • setOnHoverListener(MotionEvent::InputEvent):AR眼镜等增强现实设备。

5)Hover事件分发:当前View在焦点移出(不再是Hover状态)时,不会立即发送ACTION_HOVER_EXIT退出事件,需要等到下一个View获取到ACTION_HOVER_ENTER状态时才会发送上一个View的ACTION_HOVER_EXIT退出事件。

6)窗口内部View的Hover事件转化过程

  • RootView会先获取到ACTION_HOVER_ENTER事件;
  • 当进入ChildView时,ChildView会先获取到ACTION_HOVER_ENTER事件,然后RootView会获取到ACTION_HOVER_EXIT事件;
  • 当从ChildView退出时,ChildView会先获取到ACTION_HOVER_EXIT事件,然后RootView会获取到ACTION_HOVER_ENTER事件。

1.2 问题概述

        问题描述:在Android悬浮弹窗上双击返回,主界面响应返回事件。

        问题原因:悬浮弹窗设置了flag为窗口不可获取焦点即:WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE。

        问题分析

  • 悬浮弹窗设置flag为窗口不可获取焦点,是为了不影响主界面的焦点响应(Android默认主界面的窗口是获取焦点的);
  • 如果悬浮弹窗设置flag可获取焦点,那么Android的事件分发是无法发送到主界面的,会将事件分发给当前可获取焦点的悬浮窗口;
  • 如下图,左侧图1为悬浮窗口,右侧图2为主界面某应用打开一个Activity。图1悬浮窗口是常驻于图2主界面的左侧,且默认不可获取焦点,但在特请情况时可获取焦点(如展开键盘、焦点在此悬浮窗口内部)。

        解决方案:当焦点在悬浮窗口内部时,设置窗口flag可获取焦点;当焦点不在悬浮窗口内部时,设置窗口flag不可获取焦点。

2 解决方案

        方案主要分为如下几步:

  1. 窗口默认不可获取焦点;
  2. 窗口监听焦点的移入/移出事件;
  3. 窗口监听到焦点移入,判断窗口是否可获取焦点,否——设置窗口可获取焦点,是——不做任何操作;
  4. 窗口监听到焦点移出,判断焦点是否在窗口内部,否——设置窗口不可获取焦点,是——不做任何操作;

        读者可思考如下2个问题,

1)问题1:为什么在窗口监听到焦点移入后,要再判断窗口是否可获取焦点?

2)问题2:为什么在窗口监听到焦点移出后,要再判断焦点是否在窗口内部?

        相信本文《1.1 准备知识的第6部分》可以给你一些灵感。   

     

3 代码部分

3.1 动态更新窗口焦点

        核心API:

  • WindowManager.updateViewLayout
  • WindowManager.LayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
    private fun updateNotificationParams(focusable: Boolean) {
        initLayoutParams(focusable)
        mUiHandler.post {
            synchronized(this) {
                if (mIsBarWindowAdded) {
                    try {
                        mWindowManager.updateViewLayout(mNotificationBar, mLayoutParams)
                    } catch (e: Exception) {
                        e.printStackTrace()
                    }
                }
            }
        }
    }


    private fun initLayoutParams(focusable: Boolean) {
        mLayoutParams = WindowManager.LayoutParams().apply {
            type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
            val density = mContext.resources.displayMetrics.density
            width = (640 * density).toInt()
            height = (640 * density).toInt()
            flags =
                WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
            if (!focusable) {
                flags = flags or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
            }
            format = PixelFormat.RGBA_8888 // 去除默认时有的黑色背景,设置为全透明
            gravity = Gravity.TOP or Gravity.START
            title = MB_SYSUI_NOTIFICATION
            x = (680 * density).toInt() // adb shell wm size 1920x1280
            y = 0
            setTranslationZ(TRANSLATION_Z_200CM)
            setRotationYAroundOrigin(22.0f)
        }
    }
    

3.2 窗口监听返回事件

        在自定义View中重写dispatchKeyEvent方法,监听keyCode == KeyEvent.KEYCODE_BACK事件即可。

    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
        if (event.keyCode == KeyEvent.KEYCODE_BACK) {
            Log.i(TAG, "dispatchKeyEvent: KEYCODE_BACK")
            LiveDataBus.get().with(Constants.NOTIFICATION_EVENT_BUS_CLOSE_FOLD_PAGE).value = true
        }
        return super.dispatchKeyEvent(event)
    }

3.3 判断焦点是否在窗口内部

    mRootView.post {
        val locationXY = IntArray(2)
        mRootView.getLocationOnScreen(locationXY)
        val locationX = locationXY[0]
        val locationY = locationXY[1]
        val measuredWidth = mRootView.measuredWidth
        val measuredHeight = mRootView.measuredHeight
    }



    /**
     * 焦点:就是Hover点、中央注视点、可与用户交互的点。
     *
     * if (rawX < locationX || rawX > locationX + measuredWidth || rawY < locationY || rawY > locationY + measuredHeight) {
     *     // 焦点不在View内部
     *     Log.i(TAG, "isViewNotFocus: 焦点不在View内部")
     * } else {
     *     // 焦点在View内部
     *     Log.i(TAG, "isViewNotFocus: 焦点在View内部")
     * }
     *
     * @param locationX View相对于屏幕位置X
     * @param locationY View相对于屏幕位置Y
     * @param measuredWidth View宽
     * @param measuredHeight View高
     * @param rawX 焦点相对于屏幕位置X
     * @param rawY 焦点相对于屏幕位置Y
     *
     * @return 焦点是否未在View内部
     */
    private fun isViewNotFocus(
        locationX: Int,
        locationY: Int,
        measuredWidth: Int,
        measuredHeight: Int,
        rawX: Float,
        rawY: Float
    ): Boolean {
        val density = context.resources.displayMetrics.density
        return rawX <= locationX + 50 * density || rawX >= locationX + measuredWidth - 100 * density || rawY <= locationY + 15 * density || rawY >= locationY + measuredHeight - 60 * density
    }

3.4 窗口监听焦点移入/移出

// 注:Focus移出时需要包含边界。
            mRootView.setOnHoverListener { v, event ->
                when (event.action) {
                    MotionEvent.ACTION_HOVER_ENTER -> {
                        Log.i(
                            TAG,
                            "OnHoverListener: 进入, action =  ${event.action},motionX = ${event.rawX},motionY = ${event.rawY}"
                        )
                        LiveDataBus.get().with(NOTIFICATION_EVENT_BUS_FOCUSABLE).value?.let {
                            if (!(it as Boolean)) {
                                Log.i(TAG, "OnHoverListener: 进入, focus-true-0000")
                                LiveDataBus.get().with(NOTIFICATION_EVENT_BUS_FOCUSABLE).value =
                                    true
                            }
                        } ?: let {
                            Log.i(TAG, "OnHoverListener: 进入, focus-true-1111")
                            LiveDataBus.get().with(NOTIFICATION_EVENT_BUS_FOCUSABLE).value = true
                        }
                    }

                    MotionEvent.ACTION_HOVER_MOVE -> {
                    }

                    MotionEvent.ACTION_HOVER_EXIT -> {
                        Log.i(
                            TAG,
                            "OnHoverListener: 退出, action =  ${event.action},motionX = ${event.rawX},motionY = ${event.rawY}"
                        )
                        if (isViewNotFocus(
                                locationX,
                                locationY,
                                measuredWidth,
                                measuredHeight,
                                event.rawX,
                                event.rawY
                            )
                        ) {
                            Log.i(TAG, "OnHoverListener: 退出, focus-false")
                            LiveDataBus.get().with(NOTIFICATION_EVENT_BUS_FOCUSABLE).value = false
                        }
                    }
                }
                false
            }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/455114.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Burp Suite Professional Error No response received from remote server.

记录burp suite 抓到包-改包-放包之后出现的问题&#xff1a; Burp Suite Professional Error No response received from remote server. 重新下载软件&#xff0c;没有进行汉化&#xff0c;好用了。汉化真坑。

buuctf warmup 超详细

目录 1.代码审计&#xff1a; 2.逻辑分析 3.总结分析 4.分析记录 5.疑点解答 1.代码审计&#xff1a; <?phphighlight_file(__FILE__);class emmm //定义了一个类{public static function checkFile(&$page) 类里面又申明创建…

论文阅读——RemoteCLIP

RemoteCLIP: A Vision Language Foundation Model for Remote Sensing 摘要——通用基础模型在人工智能领域变得越来越重要。虽然自监督学习&#xff08;SSL&#xff09;和掩蔽图像建模&#xff08;MIM&#xff09;在构建此类遥感基础模型方面取得了有希望的结果&#xff0c;但…

【JavaScript】面试手撕柯里化函数

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 引入柯里化定义实现快速使用柯里化的作用提高自由度bind函数 参考资料 引入 上周…

目标跟踪SORT算法原理浅析

SORT算法 Simple Online and Realtime Tracking(SORT)是一个非常简单、有效、实用的多目标跟踪算法。在SORT中&#xff0c;仅仅通过IOU来进行匹配虽然速度非常快&#xff0c;但是ID switch依然非常严重。 SORT最大特点是基于Faster RCNN的目标检测方法&#xff0c;并利用卡尔…

跟着GPT学设计模式之桥接模式

说明 桥接模式&#xff0c;也叫作桥梁模式&#xff0c;英文是 Bridge Design Pattern。在 GoF 的《设计模式》一书中&#xff0c;桥接模式是这么定义的&#xff1a;“Decouple an abstraction from its implementation so that the two can vary independently。”翻译成中文就…

【Ubuntu-20.04】OpenCV-3.4.16的安装并对图片与视频处理

【Ubuntu-20.04】OpenCV-3.4.16的安装并对图片与视频处理 一、安装OpenCV-3.4.161.下载OpenCV-3.4.16安装包2.将安装包放到/home&#xff0c;并解压3.使用 cmake 安装 opencv4.配置环境5.查看 opencv 的版本信息 二、处理图片&#xff08;一&#xff09;创建文件夹 code &#…

深入理解Python中的面向对象编程(OOP)【第129篇—Scikit-learn的入门】

深入理解Python中的面向对象编程&#xff08;OOP&#xff09; 在Python编程领域中&#xff0c;面向对象编程&#xff08;Object-Oriented Programming&#xff0c;简称OOP&#xff09;是一种强大而灵活的编程范式&#xff0c;它允许开发者以对象为中心组织代码&#xff0c;使得…

错误: 找不到或无法加载主类 Hello.class

在运行这串代码 public class Hello{ public static void main(String[] args){ System.out.println("Hello world!"); } } 的时候出现报错&#xff1a;错误: 找不到或无法加载主类 Hello.class 入门级错误 1.公共类的文件名和类名不一致 hello.j…

2024国际数字体育科技与电子竞技博览会在深圳前海隆重召开

随着科技的飞速发展,数字体育与电子竞技日益成为全球关注的焦点。3月2日,由中国电子商会数字体育与电子竞技专业委员会指导、赛艾特会展(深圳)有限公司、深圳国合华鑫科技发展有限公司、通联(深圳)数字科技集团有限公司联合主办的2024国际数字体育科技与电子竞技博览会新闻发布…

面试题 --- jdbc执行流程、MyBatis执行流程、MyBatis拦截器配置流程

jdbc执行流程 1. 注册驱动 2. 创建数据库操作对象 3. 执行sql语句 4 .处理操作结果 5 .关闭连接释放资源 MyBatis 执行流程 Executor执行器、MappedStatement 对象、 StatementHandler 语句处理器 关系可以用以下步骤概括 用户通过 SqlSession 调用一个方法&#xff0c;Sq…

音视频开发之旅(75)- AI数字人进阶--GeneFace++

目录 1.效果展示和玩法场景 2.GeneFace原理学习 3.数据集准备以及训练的过程 5.遇到的问题与解决方案 6.参考资料 一、效果展示 AI数字人进阶--GeneFace&#xff08;1&#xff09; AI数字人进阶--GeneFace&#xff08;2&#xff09; 想象一下&#xff0c;一个专为你打造的…

DVWA 靶场搭建

文章目录 1 DVWA 简介2 DVWA 安装 1 DVWA 简介 DVWA&#xff08;Damn Vulnerable Web App&#xff09;是一个基于 “PHP MySQL” 搭建的Web应用程序&#xff0c;皆在为安全专业人员测试自己的专业技能和工具提供合法的环境&#xff0c;帮助Web开发者更好地理解Web应用安全防范…

fs模块 文件写入 之 异步写入与同步写入

一、fs模块介绍&#xff1a; fs&#xff08;file system&#xff09;模块是nodejs提供的用于访问本地文件系统的功能模块&#xff0c;它使得运行于nodejs环境下的JavaScript具备直接读写本地文件的能力。 fs模块是nodejs的核心模块之一&#xff0c;只要安装了nodejs&#xff…

华为配置OSPF的Stub区域示例

配置OSPF的Stub区域示例 组网图形 图1 配置OSPF Stub区域组网图 Stub区域简介配置注意事项组网需求配置思路操作步骤配置文件 Stub区域简介 Stub区域的ABR不传播它们接收到的自治系统外部路由&#xff0c;在Stub区域中路由器的路由表规模以及路由信息传递的数量都会大大减少…

【C++】string进一步介绍

个人主页 &#xff1a; zxctscl 如有转载请先通知 文章目录 1. 前言2. 迭代器2.1 反向迭代器2.2 const对象迭代器 3. Capacity3.1 size和length3.2 max_size3.3 capacity3.4 clear3.5 shrink_to_fit &#xff08;了解即可&#xff09;3.6 reserve3.7 resize 4. Element access4…

一台服务器部署两个独立的mysql实例

&#x1f341;博主简介&#xff1a; &#x1f3c5;云计算领域优质创作者 &#x1f3c5;2022年CSDN新星计划python赛道第一名 &#x1f3c5;2022年CSDN原力计划优质作者 &#x1f3c5;阿里云ACE认证高级工程师 &#x1f3c5;阿里云开发者社区专…

STM32平替GD32有多方便

众所周知, GD32一直模仿STM32,从未被超越。 我最近公司使用的GD32E230C6T6 这款芯片有48个引脚。 属于小容量的芯片。 我有一个用STM32写的代码,之前是用的 STM32F103CB 这款芯片是中容量的。 不过在keil中,只需要这两步,就能使用原来的逻辑,几乎不用修改代码。 1. …

【Swing】Java Swing实现省市区选择编辑器

【Swing】Java Swing实现省市区选择编辑器 1.需求描述2.需求实现3.效果展示 系统&#xff1a;Win10 JDK&#xff1a;1.8.0_351 IDEA&#xff1a;2022.3.3 1.需求描述 在公司的一个 Swing 的项目上需要实现一个选择省市区的编辑器&#xff0c;这还是第一次做这种编辑器&#xf…

【数据结构】二叉树OJ题目

965. 单值二叉树 如果二叉树每个节点都具有相同的值&#xff0c;那么该二叉树就是单值二叉树。 只有给定的树是单值二叉树时&#xff0c;才返回 true&#xff1b;否则返回 false。 示例 1&#xff1a; 输入&#xff1a;[1,1,1,1,1,null,1] 输出&#xff1a;true示例 2&#x…