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

news2025/1/9 3:56:17

目录

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
            }

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

FFmpeg工作流程及视频文件分析

FFmpeg工作流程: 解封装(Demuxing)--->解码(Decoding)--->编码(Encoding)--->封装(Muxing) FFmpeg转码工作流程: 读取输入流--->音视频解封装--->解码音视频帧--->编码音视频帧--->音视频封装--->输出目标流 可简单理解为如下流程: 读文件-->解…

教师如何搭建学生查询考试分数的平台?

随着信息技术的快速发展&#xff0c;搭建一个学生查询考试分数的平台已经成为现代教育管理的重要组成部分。这样的平台不仅可以提高成绩管理的效率&#xff0c;还能为学生提供便捷、及时的成绩查询服务。那么&#xff0c;作为教师&#xff0c;我们应该如何搭建这样一个平台呢&a…

计算机网络期末98+冲刺笔记

一、计算机网络基础 1.1计算机网络的概述 计算机网络的定义&#xff1a;利用通信设备和线路&#xff0c;将地理位置不同的具有独立功能的多台计算机机器外部设备连接起来&#xff0c;在网络操作系统、网络管理软件及网络通信协议的管理和协调下&#xff0c;实现资源共享和信息…

力扣977. 有序数组的平方

思路&#xff1a;暴力法&#xff1a;全部平方&#xff0c;然后调用排序API&#xff0c;排序算法最快是N*log(N)时间复制度。 双指针法&#xff1a;要利用好原本的数组本就是有序的数组这个条件&#xff0c; 只是有负数 导致平方后变大了&#xff0c;那么平方后的最大值就是在两…

YOLOv5+DeepSort的汽车流量统计

前言 先来看下实现效果&#xff1a; 上图展示了用yolov5作为检测器&#xff0c;DeepSort为追踪器实现了对车流量的统计并绘制了每辆车的运行轨迹。 一、整体目录结构 下图展示了项目的整体目录结构&#xff1a; 其中&#xff1a; deep_sort文件下为目标跟踪相关代码&#x…

力扣串题:验证回文串2

bool judge(char *s,int m,int n){while(m<n){if(s[m]!s[n]){return false;}m,n--;}return true; } bool validPalindrome(char * s){for(int i0,jstrlen(s)-1;i<j;i,j--){if(s[i]!s[j]){return (judge(s,i1,j)||judge(s,i,j-1));}}return true; }这个题直接背大佬代码吧…

记录一下在Pycharm中虚拟环境的创建

如果在Pycharm中要新建一个虚拟环境&#xff0c;那你可以在Terminal中选择Command Prompt&#xff0c;在这里面执行相关命令 一、安装了Anaconda&#xff0c;创建虚拟环境 当你使用解释器是Anaconda提供的时&#xff0c;你可以使用conda命令执行&#xff0c;见以下操作&#x…

自适应窗口图片轮播HTML代码

自适应窗口图片轮播HTML代码&#xff0c;源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面&#xff0c;重定向这个界面 代码下载地址 自适应窗口图片轮播HTML代码

pkav之当php懈垢windows通用上传缺陷

环境&#xff1a; Windowsnginxphp 一、php源码 <?php //U-Mail demo ... if(isset($_POST[submit])){$filename $_POST[filename];$filename preg_replace("/[^\w]/i", "", $filename);$upfile $_FILES[file][name];$upfile str_replace(;,&qu…

【Flink SQL】Flink SQL 基础概念:SQL 动态表 连续查询

Flink SQL 基础概念&#xff1a;SQL 动态表 & 连续查询 1.SQL 应用于流处理的思路2.流批处理的异同点及将 SQL 应用于流处理核心解决的问题3.SQL 流处理的输入&#xff1a;输入流映射为 SQL 动态输入表4.SQL 流处理的计算&#xff1a;实时处理底层技术 - SQL 连续查询5.SQL…

Windows Server 各版本搭建 Web 服务器实现访问本地 Web 网站(03~19)

一、Windows Server 2003 点击左下角开始➡管理工具➡管理您的服务器&#xff0c;点击添加或删除角色 点击下一步 选择自定义&#xff0c;点击下一步 选择应用程序服务器&#xff0c;点击下一步 不勾选&#xff0c;点击下一步 这里提示插入磁盘&#xff0c;咱们提前下载好 IIS…

SinoDB海洋渔业时序数据解决方案

一、海洋渔业平台 介绍 福建理工大学针对我国浅海增养殖信息化和智能化程度低、多源数据库缺乏、大数据挖掘与分析技术薄弱等问题&#xff0c;构建了海洋渔业平台。 该平台方案使用了星瑞格数据库管理系统&#xff08;下文简称&#xff1a;SinoDB&#xff09;&#xff0c;充分利…

在Linux中进行OpenSSH升级

由于OpenSSH有严重漏洞&#xff0c;因此需要升级OpenSSH到最新版本。 OpenSSL和OpenSSH都要更新&#xff0c;OpenSSH依赖于OpenSSL。 第一步&#xff0c;查看当前的OpenSSH服务版本。 命令&#xff1a;ssh -V 第二步&#xff0c;安装、启动telnet&#xff0c;关闭安全文件&a…

使用CrossOver 在Mac 运行Windows 软件|D3DMetal是什么技术,

CrossOver Mac 使用特点 • 免费试用 14 天&#xff0c;可使用 CrossOver Mac 全部功能&#xff0c;• 试用过期会保留之前安装的 Windows 软件• 使 Mac 运行 Windows 程序 使用CrossOver在Mac上运行Windows软件是一个方便且无需安装完整Windows操作系统的解决方案。CrossOve…

Linux下的编辑器——Vim

vi/vim 的区别简单点来说&#xff0c;它们都是多模式编辑器&#xff0c;不同的是 vim 是 vi 的升级版本&#xff0c;它不仅兼容 vi 的所有指令&#xff0c;而且还有一些新的特性在里面。例如语法加亮&#xff0c;可视化操作不仅可以在终端运行&#xff0c;也可以运行于x window…

SpringBoot(Lombok + Spring Initailizr + yaml)

1.Lombok 1.基本介绍 2.应用实例 1.pom.xml 引入Lombok&#xff0c;使用版本仲裁 <!--导入springboot父工程--><parent><artifactId>spring-boot-starter-parent</artifactId><groupId>org.springframework.boot</groupId><version&g…

20240313寻找集成联调交付的具体方式

集成联调交付&#xff08;Integrated Joint Debugging and Delivery&#xff09;是软件开发过程中的一个阶段&#xff0c;主要涉及将不同的软件模块或组件整合在一起&#xff0c;并进行联合调试和测试&#xff0c;以确保它们能够作为一个整体正常工作。这个过程通常发生在开发周…

DMSP夜间灯光卫星介绍和数据下载

DMSP(Defense Meteorological Sate-llite Program)是美国国防部的极轨卫星计划&#xff0c;与NOAA卫星同属于一类&#xff0c;只不过星上载荷不同。 DMSP卫星简介 现有DMSP为三轴姿态稳定卫星&#xff0c;运行在高度约830km的太阳同步轨道&#xff0c;周期约101min&#xff0c…

安卓六大布局

LinearLayout&#xff08;线性布局&#xff09; 1.简介 线性布局在开发中使用最多&#xff0c;具有垂直方向与水平方向的布局方式。LinearLayout 默认是垂直排列的&#xff0c;但是可以通过设置 android:orientation 属性来改变为水平排列。 2.常用属性 orientation&#xf…

docker修改配置文件后一直显示Restarting (1) 状态

docker修改配置文件后一直显示Restarting 状态 一、问题描述 一、问题描述 当我在修改nginx的配置文件之后&#xff0c;一直出现Restarting 状态&#xff0c;并且无法成功访问&#xff0c;如下图所示 然后查看nginx的日志&#xff0c;如下所示&#xff1a; 因为我修改的时配…