如何处理Android悬浮弹窗双击返回事件?
目录
1 前言
1.1 准备知识
1.2 问题概述
2 解决方案
3 代码部分
3.1 动态更新窗口焦点
3.2 窗口监听返回事件
3.3 判断焦点是否在窗口内部
3.4 窗口监听焦点移入/移出
4 注意事项
4.1 窗口范围
4.2 空隙处的返回事件处理
1 前言
1.1 准备知识
1)开发环境:
- 2D开发环境:所有界面或窗口都在主界面显示;
- 3D开发环境:保留原生Android的主界面,在主界面之外绘制各种窗口,配合3D渲染以实现3D效果。
2)焦点:就是Hover点、中央注视点、可与用户交互的点。
3)窗口:就是系统窗口、悬浮弹窗,内部通过addView方法去添加View,本文窗口监听指的就是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 解决方案
方案主要分为如下几步:
- 窗口默认不可获取焦点;
- 窗口监听焦点的移入/移出事件;
- 窗口监听到焦点移入,判断窗口是否可获取焦点,否——设置窗口可获取焦点,是——不做任何操作;
- 窗口监听到焦点移出,判断焦点是否在窗口内部,否——设置窗口不可获取焦点,是——不做任何操作;
读者可思考如下2个问题,
1)问题1:为什么在窗口监听到焦点移入后,要再判断窗口是否可获取焦点?
2)问题2:为什么在窗口监听到焦点移出后,要再判断焦点是否在窗口内部?
相信本文《1.1 准备知识的Hover事件分发部分》可以给你一些灵感。
3 代码部分
3.1 动态更新窗口焦点
核心API:
- WindowManager.updateViewLayout;
- WindowManager.LayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
private fun initLiveDataBus() {LiveDataBus.get().with(Constants.NOTIFICATION_EVENT_BUS_FOCUSABLE, Boolean::class.java).observeForever { focusable: Boolean ->Log.d(TAG, "onChanged: $focusable")updateNotificationParams(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_OVERLAYval density = mContext.resources.displayMetrics.densitywidth = (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_LIMITSif (!focusable) {flags = flags or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE}format = PixelFormat.RGBA_8888 // 去除默认时有的黑色背景,设置为全透明gravity = Gravity.TOP or Gravity.STARTtitle = SYSUI_NOTIFICATIONx = -(640 * density).toInt()y = 0}}
3.2 窗口监听返回事件
窗口设置可获得焦点后,内部View会获取到事件分发的事件,在此View中重写dispatchKeyEvent方法,监听keyCode == KeyEvent.KEYCODE_BACK事件,就可对返回事件进行处理。
override fun dispatchKeyEvent(event: KeyEvent): Boolean {if (event.keyCode == KeyEvent.KEYCODE_BACK) {Log.i(TAG, "dispatchKeyEvent: KEYCODE_BACK")// 窗口设置可获得焦点后,内部View会获取到事件分发的事件,并可对返回事件进行处理}return super.dispatchKeyEvent(event)}
3.3 判断焦点是否在窗口内部
通过View相对于屏幕位置X/Y、以及View宽高,共同确定View的边界。
mRootView.post {val locationXY = IntArray(2)mRootView.getLocationOnScreen(locationXY)val locationX = locationXY[0]val locationY = locationXY[1]val measuredWidth = mRootView.measuredWidthval measuredHeight = mRootView.measuredHeight}/*** 焦点:就是Hover点、中央注视点、可与用户交互的点。** @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) =if (rawX <= locationX || rawX >= locationX + measuredWidth || rawY <= locationY || rawY >= locationY + measuredHeight) {// 焦点不在View内部Log.i(TAG, "isViewNotFocus: 焦点不在View内部")true} else {// 焦点在View内部Log.i(TAG, "isViewNotFocus: 焦点在View内部")false}
3.4 窗口监听焦点移入/移出
- 窗口监听到焦点移入,判断窗口是否可获取焦点,否——设置窗口可获取焦点,是——不做任何操作;
- 窗口监听到焦点移出,判断焦点是否在窗口内部,否——设置窗口不可获取焦点,是——不做任何操作;
- 最后,通过发送NOTIFICATION_EVENT_BUS_FOCUSABLE事件,进而设置窗口的是否可获取焦点。
// 注: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-again")LiveDataBus.get().with(NOTIFICATION_EVENT_BUS_FOCUSABLE).value =true}} ?: let {Log.i(TAG, "OnHoverListener: 进入, focus-true-init")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}
4 注意事项
4.1 窗口范围
在判断焦点是否在窗口内部时,需要确认窗口范围,如果窗口内部的View有设置Padding或Margin,应该将其去掉。
如:本文的窗口大小是640*640,但View大小是540*580,所以计算时需要去掉相应Padding或Margin,重写isViewNotFocus()方法如下:
private fun isViewNotFocus(locationX: Int,locationY: Int,measuredWidth: Int,measuredHeight: Int,rawX: Float,rawY: Float): Boolean {val density = context.resources.displayMetrics.densityreturn rawX <= locationX + 50 * density || rawX >= locationX + measuredWidth - 100 * density || rawY <= locationY + 15 * density || rawY >= locationY + measuredHeight - 60 * density}
4.2 空隙处的返回事件处理
1)从窗口移出到空隙处
通过本文1.1准备知识的第5部分《Hover事件分发》,我们知道,从窗口移出但还未有下一个View获取焦点时,此时窗口还是会接收到返回事件。
2)从View移出到空隙处
从当前View移出但还未有下一个View获取焦点时,此时当前View还是会接收到返回事件。
那么,如何处理这种空隙处的返回事件呢?
核心:从系统层拦截此种情况下的返回事件 。
- 渲染层:提供接口,返回焦点移入移出时当前layer的名称,是否有碰撞窗口等信息;
- 系统层:当没有碰撞窗口时,从系统层拦截掉返回事件的分发;
- 应用层:监听焦点移入移出,改变窗口focus属性,并处理返回事件;
解决方案:
当空隙处有返回事件产生时,系统层通过渲染层的接口,获取到当前焦点所在位置的layer名称,如果layer名称为空则断定为空隙处,直接做拦截处理,不再往应用层分发。
注:每个窗口、Activity在其Window中,都有设置其title属性,layer名称就是此title属性的值。
目录
1 前言
1.1 准备知识
1.2 问题概述
2 解决方案
3 代码部分
3.1 动态更新窗口焦点
3.2 窗口监听返回事件
3.3 判断焦点是否在窗口内部
3.4 窗口监听焦点移入/移出
4 注意事项
4.1 窗口范围
4.2 空隙处的返回事件处理
相关文章:
如何处理Android悬浮弹窗双击返回事件?
目录 1 前言 1.1 准备知识 1.2 问题概述 2 解决方案 3 代码部分 3.1 动态更新窗口焦点 3.2 窗口监听返回事件 3.3 判断焦点是否在窗口内部 3.4 窗口监听焦点移入/移出 4 注意事项 4.1 窗口范围 4.2 空隙处的返回事件处理 1 前言 1.1 准备知识 1)开发环…...
高可用篇_A Docker容器化技术_II Docker环境搭建和常见命令
原创作者:田超凡(程序员田宝宝) 版权所有,引用请注明原作者,严禁复制转载 Docker安装 Docker 要求 CentOS7 系统的内核版本在 3.10以上 ,查看本页面的前提条件来验证你的CentOS 版本是否支持 Docker 。 …...
Vue.js+SpringBoot开发食品生产管理系统
目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 加工厂管理模块2.2 客户管理模块2.3 食品管理模块2.4 生产销售订单管理模块2.5 系统管理模块2.6 其他管理模块 三、系统展示四、核心代码4.1 查询食品4.2 查询加工厂4.3 新增生产订单4.4 新增销售订单4.5 查询客户 五、…...
Python面试笔记
Python面试笔记 PythonQ. Python中可变数据类型与不可变数据类型,浅拷贝与深拷贝详解Q. 解释什么是lambda函数?它有什么好处?Q. 什么是装饰器?Q. 什么是Python的垃圾回收机制?Q. Python内置函数dir的用法?Q…...
springboot 查看和修改内置 tomcat 版本
解析Spring Boot父级依赖 去到项目的根pom文件中,找到parent依赖: <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>${springboot.version}…...
003——移植鸿蒙
目录 一、顶层Make分析 二、添加一个新的单板 2.1 Kconfig 2.2 Makefile 2.2.1 顶层Makefile 2.2.2 platform下的Makefile 2.2.3 platform下的bsp.mk文件 2.3 编译与调试 2.4 解决链接错误 三、内核启动流程的学习 3.1 韦东山老师总结的启动四步 3.2 启动文件分析…...
罗马数字转整数-力扣通过自己编译器编译
学会将力扣题目用自己自带的编译软件编译---纯自己想的本题解法 字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000 例如, 罗马数字 2 写做 II ,即为两…...
深入解析JVM加载机制
一、背景 Java代码被编译器变成生成Class字节码,但字节码仅是一个特殊的二进制文件,无法直接使用。因此,都需要放到JVM系统中执行,将Class字节码文件放入到JVM的过程,简称类加载。 二、整体流程 三、阶段逻辑分析 3…...
python redis中blpop和lpop的区别
python redis中lpop()方法是获取并删除左边第一个对象。 def lpop(self,name: str,count: Optional[int] None,) -> Union[Awaitable[Union[str, List, None]], Union[str, List, None]]:"""Removes and returns the first elements of the list name.By de…...
第四百一十回
文章目录 1. 概念介绍2. 方法与细节2.1 获取方法2.2 使用细节 3. 示例代码4. 内容总结 我们在上一章回中介绍了"如何获取当前系统语言"相关的内容,本章回中将介绍如何获取时间戳.闲话休提,让我们一起Talk Flutter吧。 1. 概念介绍 我们在本章…...
程序员的README——编写可维护的代码(一)
用户行为不可预测,网络不可靠,事情总会出错。生产环境下的软件必须一直保持可用状态。 编写可维护的代码有助于你应对不可预见的情况,可维护的代码有内置的保护、诊断和控制。 切记通过安全和有弹性的编码实践进行防御式编程来保护你的系统&a…...
数据库管理-第160期 Oracle Vector DB AI-11(20240312)
数据库管理160期 2024-03-12 数据库管理-第160期 Oracle Vector DB & AI-11(20240312)1 向量的函数操作to_vector()将vector转换为标准值vector_norm()vector_dimension_count()vector_dimension_format() 2 将向量转换为字符串或CLOBvector_seriali…...
(C++进阶)boost库笔记
目录 1、boost::function 1.1 概述 1.2 boost包装器和C11包装器对比 1.2、代码示例 1、boost::function 1.1 概述 boost::function 是 Boost 库中提供的一个通用函数对象包装器,它可以存储指向任何可调用对象的指针,并且可以在任何时候通过 operat…...
MapReduce面试重点
文章目录 1. 简述MapReduce整个流程2. join原理 1. 简述MapReduce整个流程 数据划分(Input Splitting):开始时,输入数据被分割成逻辑上的小块,每个块被称为Input Split。 映射(Map):每个Input Split 由一个或多个Map任务处理&…...
C语言简单题(7)从主函数中输入10个等长字符串,用一个函数对他们排序,然后在主函数输出这10个已排好序的字符串
从主函数中输入10个等长字符串,用一个函数对他们排序,然后在主函数输出这10个已排好序的字符串 /* 从主函数中输入10个等长字符串,用一个函数对他们排序,然后在主函数输出这10个已排好序的字符串 */ #include<stdio.h> …...
光伏科普|太阳能光伏发电应用场景有哪些?
太阳能光伏发电的应用领域其实非常广泛,很多人会不相信,但在我们的日常生活中随处可见太阳能光伏产业,本文将详细介绍其应用场景有哪些。 一、工业领域厂房 太阳能光伏发电作为一种清洁、可再生的能源,安装在工业领域厂房&#…...
Go 构建高效的二叉搜索树联系簿
引言 树是一种重要的数据结构,而二叉搜索树(BST)则是树的一种常见形式。在本文中,我们将学习如何构建一个高效的二叉搜索树联系簿,以便快速插入、搜索和删除联系人信息。 介绍二叉搜索树 二叉搜索树是一种有序的二叉…...
基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的交通信号灯识别系统(深度学习+UI界面+训练数据集+Python代码)
摘要:本研究详细介绍了一种采用深度学习技术的交通信号灯识别系统,该系统集成了最新的YOLOv8算法,并与YOLOv7、YOLOv6、YOLOv5等早期算法进行了性能评估对比。该系统能够在各种媒介——包括图像、视频文件、实时视频流及批量文件中——准确地…...
以太坊开发学习-solidity(三)函数类型
目录 函数类型 函数类型 solidity官方文档里把函数归到数值类型 函数类型是一种表示函数的类型。可以将一个函数赋值给另一个函数类型的变量, 也可以将一个函数作为参数进行传递,还能在函数调用中返回函数类型变量。 函数类型有两类:- 内部&…...
教你把公司吃干抹净、榨干带走
大家好: 衷心希望各位点赞。 您的问题请留在评论区,我会及时回答 正文 打工人一定要做到够自私,把公司的一切为我所用,你要知道闷头打工是没有出路的。聪明的人会以最快的速度榨干带走公司的一切资源、人脉、技能,为…...
开发指南007-导出Excel
平台上开发导出Excel比过去的单体架构要复杂些,因为前端和后台不在一个进程空间里。 后台的操作是先生成excel文件,技术路线是jxl <dependency><groupId>net.sourceforge.jexcelapi</groupId><artifactId>jxl</artifactId&g…...
滑块验证码
1.这里针对滑块验证给了一个封装的组件verifition,使用直接可以调用 2.组件目录 3.每个文件的内容 3.1 Api文件中只有一个index.js文件,用来存放获取滑块和校验滑块结果的api import request from /router/axios//获取验证图片 export function reqGe…...
cmd常用指令
cmd全称Command Prompt,中文译为命令提示符。 命令提示符是在操作系统中,提示进行命令输入的一种工作提示符。 在不同的操作系统环境下,命令提示符各不相同。 在windows环境下,命令行程序为cmd.exe,是一个32位的命令…...
【嵌入式DIY实例】-DIY手势识别和颜色识别(基于APDS9960)
DIY手势识别和颜色识别(基于APDS9960) 文章目录 DIY手势识别和颜色识别(基于APDS9960)1、硬件准备2、APDS9960 手势识别传感器介绍3、硬件接线4、代码实现4.1 手势识别4.2 颜色识别4.3 趋近感应代码5、综合实例代码在本文中,我们将介绍 APDS9960 手势、RGB 和接近传感器与…...
python 直方图
python可以调用hist方法绘制直方图。 import matplotlib.pyplot as plt import numpy as np; plt.rcParams["font.family"]["SimHei"] # 确保图中中文字体正确显示 x[0.1,0.2,0.3,0.4,0.5,0.6,0.1,0.2,0.2,0.2] plt.xlabel(满意程度) plt.ylabel(频数) …...
如何在数据库中使用sql语言插入数据
在SQL中,你可以使用INSERT INTO语句来添加数据到数据库表中。以下是一个基本示例,说明如何向表中插入数据: 假设你有一个名为students的表,它有以下字段:id, name, age 和 grade。 CREATE TABLE students ( id INT P…...
JVM的双亲委派模型和垃圾回收机制
jvm的作用是解释执行java字节码.java的跨平台就是靠jvm实现的.下面看看一个java程序的执行流程. 1. jvm中的内存区域划分 jvm也是一个进程,进程在运行过程中,要行操作系统申请一些资源.这些内存空间就支撑了后续java程序的执行. jvm从系统申请了一大块内存,这块内存在java程序使…...
ThreadLocal-内存泄露问题
ThreadLocal概述 ThreadLocal是多线程中对于解决线程安全的一个操作类,它会为每个线程都分配一个独立的线程副本从而解决了变量并发访问冲突的问题。ThreadLocal 同时实现了线程内的资源共享案例:使用JDBC操作数据库时,会将每一个线程的Conn…...
ISIS默认层级实验简述
ISIS被划分为三个层级:Level 1、Level 2和Level 1-2。 默认情况下,ISIS路由器属于level 1-2,是指同时支持Level 1和Level 2的路由器。路由器既可以在同一个自治系统内部进行路由选择,也可以将路由信息传递到其他自治系统。 实验拓扑图&#…...
在Flutter中创建自定义的左对齐TabBar组件
在Flutter应用程序中,TabBar是一种常见的UI模式,用于在不同的标签页之间进行导航。然而,默认情况下,Flutter的TabBar在水平方向上是居中对齐的。本文将介绍如何创建一个自定义的左对齐TabBar组件,以满足特定的布局需求…...
网站速度慢的原因/免费推广的方式
有一段时间,我一直在思考什么是团队文化,包括几年前曾经被反复提及的工程师文化等等。 但我始终觉得文化和角色之间并没有那么强的关联关系,我觉得文化和人的性格的关联度更高一些。 如果从这个角度看的话,我觉得搞技术的大致可以…...
国家企业营业执照查询系统/网站排名优化外包
RC电路在模拟电路、脉冲数字电路中得到广泛的应用,由于电 路的形式以及信号源和R,C元件参数的不同,因而组成了RC电路的各种应用形式:微分电路 、积分电路、耦合电路、滤波电路及脉冲分压器。关键词:RC电路。微分、积分…...
wordpress无限登录密码/百度竞价托管代运营
JVM内存模型总体架构图 程序计数器 多线程时,当线程数超过CPU数量或CPU内核数量,线程之间就要根据时间片轮询抢夺CPU时间资源。因此每个线程有要有一个独立的程序计数器,记录下一条要运行的指令。线程私有的内存区域。如果执行的是JAVA方法&a…...
网站建设怎么开票/凡科建站怎么用
js 行为 dom(document obiect model)文档对象模型 dom(document obiect model)浏览器对象模型 ECMAScript Ecme 标准 var box domdocument.getElementById("box")consike.log(box)var a "1111s";js数据类型…...
电子政务网站建设方案/建立网站流程
一、整体框架 asp.net core MVC ajax调用 二、 (1)GET ?带参数 前端: var url APIDomain "User/TestMethod?paramxiaoming"; ajax("GET",url) 后端: [FromQuery] param (2)GET Fr…...
电商网站开发python/新媒体营销推广方案
最近2年来,Smartbi因“真Excel”的电子表格功能被广泛认可,最近新版本研发已有成果,Smartbi决定“偷”出来给大家炫耀一下哈。 首页新风格 登陆后的首页,配色以蓝黑为主,整体感更强了;首页示例区默认了炫…...