当前位置: 首页 > news >正文

【SystemGestures】屏蔽鼠标悬浮唤出状态栏和手势导航

【SystemGestures】屏蔽鼠标悬浮唤出状态栏和手势导航

  • 一、需求描述
  • 二、解决方案
  • 三、关于 SystemGesturesPointerEventListener
    • 3.1 详细功能点代码解析
      • 1. 屏幕边缘滑动检测 (Edge Swipe Detection)
      • 2. 鼠标屏幕边缘悬浮事件 (Mouse Hover at Edges)
      • 3. 快速滑动/抛掷手势 (Fling Gesture)
      • 4. 按下与抬起/取消事件的回调 (Down, Up/Cancel)
      • 5. 多指调试手势 (Multi-finger Debug Gesture)
      • 6. 触摸点跟踪与管理
    • 3.2 Android 触摸事件的逐步处理过程
      • 步骤 1:硬件层和原生层
      • 步骤 2:关键拦截点(窗口管理器服务)
      • 步骤 3:决策——系统手势还是应用程序手势?

一、需求描述

云电脑应用界面,使用鼠标时放在屏幕边缘时会唤出状态栏和手势导航,影响正常的云电脑内部功能的使用,需要屏蔽鼠标

二、解决方案

SystemGesturesPointerEventListener.java 是 Android 系统中一个核心的输入事件监听器,它在窗口管理器(WindowManager)层面全局性地处理指针事件,主要负责识别并响应那些由系统定义、具有特殊意义的手势操作。

frameworks/base/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java

鼠标边缘悬浮的检测逻辑如下,SystemGesturesPointerEventListener 监听 ACTION_HOVER_MOVE

/*** Listens for system-wide input gestures, firing callbacks when detected.* @hide*/
class SystemGesturesPointerEventListener implements PointerEventListener {...@Overridepublic void onPointerEvent(MotionEvent event) {if (mGestureDetector != null && event.isTouchEvent()) {mGestureDetector.onTouchEvent(event);}switch (event.getActionMasked()) {...case MotionEvent.ACTION_HOVER_MOVE:if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {final float eventX = event.getX();final float eventY = event.getY();if (!mMouseHoveringAtLeft && eventX == 0) {mCallbacks.onMouseHoverAtLeft();mMouseHoveringAtLeft = true;} else if (mMouseHoveringAtLeft && eventX > 0) {mCallbacks.onMouseLeaveFromLeft();mMouseHoveringAtLeft = false;}if (!mMouseHoveringAtTop && eventY == 0) {mCallbacks.onMouseHoverAtTop();mMouseHoveringAtTop = true;} else if (mMouseHoveringAtTop && eventY > 0) {mCallbacks.onMouseLeaveFromTop();mMouseHoveringAtTop = false;}if (!mMouseHoveringAtRight && eventX >= screenWidth - 1) {mCallbacks.onMouseHoverAtRight();mMouseHoveringAtRight = true;} else if (mMouseHoveringAtRight && eventX < screenWidth - 1) {mCallbacks.onMouseLeaveFromRight();mMouseHoveringAtRight = false;}if (!mMouseHoveringAtBottom && eventY >= screenHeight - 1) {mCallbacks.onMouseHoverAtBottom();mMouseHoveringAtBottom = true;} else if (mMouseHoveringAtBottom && eventY < screenHeight - 1) {mCallbacks.onMouseLeaveFromBottom();mMouseHoveringAtBottom = false;}}break;...}}...
}

通过检测云电脑包名,在特定的包名应用中则 跳过 鼠标边缘悬浮的检测逻辑,即可防止系统级的边缘悬浮功能与云电脑应用内部的边缘操作(如调出远程桌面的菜单)产生冲突。

private boolean isIgnoringHoverMoveEvent() {// 检查当前位于前台的应用包名ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);try {ComponentName cn = am.getRunningTasks(1).get(0).topActivity;String packageName = cn.getPackageName();if (TextUtils.equals(packageName, "cm.xxx.cloudcomputerpad")) { // 云电脑的包名return true;}} catch (Exception e) {return false;}return false;
}

在 SystemGesturesPointerEventListener 中添加这个判断即可

case MotionEvent.ACTION_HOVER_MOVE:if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {// 添加这个判断即可跳过if(!isIgnoringHoverMoveEvent()) {}}

三、关于 SystemGesturesPointerEventListener

SystemGesturesPointerEventListener.java 类实现了 PointerEventListener 接口,意味着它的核心是 onPointerEvent(MotionEvent event) 方法。所有来自窗口管理器(WindowManager)的全局指针事件(触摸、鼠标等)都会被送到这里进行处理。

它内部定义了一个 Callbacks 接口,这是其与系统其他部分(通常是 DisplayPolicy)通信的桥梁。当它识别出某个手势时,就会调用这个接口中的相应方法。

3.1 详细功能点代码解析

SystemGesturesPointerEventListener 是一个功能强大且职责明确的系统级手势处理器:

  1. 核心功能是屏幕边缘滑动检测,并为刘海屏做了适配。
  2. 包含对鼠标边缘悬浮的特定处理,并展示了如何通过包名判断来添加应用排除列表,这是 OEM 定制中常见的做法。
  3. 利用 GestureDetector 来识别更复杂的手势,如 Fling,将原始的触摸事件流转化为高级的手势意图。
  4. 提供基础的事件生命周期回调 (onDown, onUpOrCancel),让系统可以同步交互状态。
  5. 内置了一个隐藏的五指调试手势,体现了其作为系统底层服务的特性。

它完美地诠释了“在系统层面监听原始输入,并将其翻译为具有特定系统意义的动作”这一设计哲学。

1. 屏幕边缘滑动检测 (Edge Swipe Detection)

这是该类的主要职责,通过 detectSwipe 方法实现。

  • 触发时机: 在 onPointerEventcase MotionEvent.ACTION_MOVE: 分支中,如果 mSwipeFireable 标志位为 true,就会调用 detectSwipe(event)mSwipeFireableACTION_DOWN 时被设为 true,在手势成功触发或事件结束后(ACTION_UP/CANCEL)被设为 false,确保一次触摸序列只触发一次滑动。

  • 核心逻辑 (detectSwipe 方法):

    1. 起始位置判断: fromY <= mSwipeStartThreshold.top (顶部), fromY >= screenHeight - mSwipeStartThreshold.bottom (底部), fromX >= screenWidth - mSwipeStartThreshold.right (右侧), fromX <= mSwipeStartThreshold.left (左侧)。这几行代码判断手指按下的初始位置 (fromX, fromY) 是否在屏幕边缘的预设阈值 (mSwipeStartThreshold) 之内。这个阈值会在 onConfigurationChanged 方法中根据屏幕尺寸和刘海(DisplayCutout)区域进行动态调整,以确保在有刘海的设备上也能正常触发。
    2. 滑动距离判断: y > fromY + mSwipeDistanceThreshold (从顶部下滑), y < fromY - mSwipeDistanceThreshold (从底部上滑) 等。这判断手指当前位置 (x, y) 相对于初始位置的移动距离是否超过了 mSwipeDistanceThreshold
    3. 超时判断: elapsed < SWIPE_TIMEOUT_MS。这确保了滑动必须在指定的时间(默认500毫秒)内完成,防止缓慢的拖动被误判为系统手势。
  • 回调触发: 一旦上述三个条件同时满足,detectSwipe 方法会返回相应的 SWIPE_FROM_* 常量,onPointerEvent 中的 switch 语句会捕获这个返回值,并调用 mCallbacks 接口中对应的方法,如 mCallbacks.onSwipeFromTop()

2. 鼠标屏幕边缘悬浮事件 (Mouse Hover at Edges)

与我之前的回答不同,这段特定的代码版本确实处理了鼠标的悬浮事件,这是一个非常重要的发现。

  • 触发时机: 在 onPointerEventcase MotionEvent.ACTION_HOVER_MOVE: 分支中。
  • 设备来源判断: event.isFromSource(InputDevice.SOURCE_MOUSE) 确保了只有当事件来自鼠标时才处理。
  • 核心逻辑:
    1. eventX == 0 (左边缘), eventY == 0 (上边缘), eventX >= screenWidth - 1 (右边缘), eventY >= screenHeight - 1 (下边缘)。代码通过判断鼠标指针的坐标 (eventX, eventY) 是否精确地位于屏幕的四个边缘,来确定鼠标是否悬浮在边缘上。
    2. 状态管理: 使用 mMouseHoveringAtLeft, mMouseHoveringAtTop 等布尔变量来跟踪当前的悬浮状态,防止重复触发回调。当鼠标进入边缘区域时,调用 onMouseHoverAt*();当鼠标离开时,调用 onMouseLeaveFrom*()

3. 快速滑动/抛掷手势 (Fling Gesture)

  • 实现方式: 通过一个内部类 FlingGestureDetector,它继承自 GestureDetector.SimpleOnGestureListenerSystemGesturesPointerEventListener 的主实例持有一个 GestureDetector 对象 (mGestureDetector)。
  • 事件传递: 在 onPointerEvent 的开头,所有触摸事件 (event.isTouchEvent()) 都会被传递给 mGestureDetector.onTouchEvent(event)
  • 核心逻辑 (FlingGestureDetector.onFling 方法):
    1. GestureDetector 从事件流中识别出 Fling 手势时,会回调 onFling 方法。
    2. 该方法使用 OverScroller 来计算抛掷动画的持续时间 (duration)。
    3. mLastFlingTime 用于防止过于频繁的 Fling 操作(两次 Fling 间隔必须大于 MAX_FLING_TIME_MILLIS)。
    4. 最后,调用 mCallbacks.onFling(duration),将计算出的动画时长传递出去,系统的其他部分可以利用这个时长来执行一个与手势速度匹配的动画。

4. 按下与抬起/取消事件的回调 (Down, Up/Cancel)

  • onDown(): 在 ACTION_DOWN 事件中被调用,通知系统一次新的触摸交互序列开始了。
  • onUpOrCancel(): 在 ACTION_UPACTION_CANCEL 事件中被调用,通知系统交互结束。这对于重置状态非常重要。

5. 多指调试手势 (Multi-finger Debug Gesture)

这是一个隐藏的开发者功能。

  • 触发时机: 在 ACTION_POINTER_DOWN 事件(即第二个或更多手指按下)时进行判断。
  • 核心逻辑: mDebugFireable = event.getPointerCount() < 5;。当按下的手指数量达到5个时 (!mDebugFireable 变为 true),会立即调用 mCallbacks.onDebug()。这通常用于触发一些调试用的覆盖层或系统信息显示。

6. 触摸点跟踪与管理

  • 数据结构: 使用 mDownPointerId, mDownX, mDownY, mDownTime 等数组来存储最多32个触摸点的初始按下信息(ID, 坐标, 时间)。
  • captureDown 方法: 在 ACTION_DOWNACTION_POINTER_DOWN 时被调用,负责记录新按下的手指信息。
  • findIndex 方法: 一个辅助方法,用于根据 pointerId 快速找到该手指在跟踪数组中的索引。

3.2 Android 触摸事件的逐步处理过程

这触及了Android输入分发机制的核心。系统需要一种方法来处理自身的手势(例如下拉状态栏),同时又不干扰应用程序的手势(例如滑动浏览照片),反之亦然。关键概念是拦截、优先和消费

以下是触摸事件的逐步处理过程,解释了系统如何决定是处理该事件还是将其传递给应用程序。想象一下,你从屏幕顶部向下滑动。

步骤 1:硬件层和原生层

  1. Hardware -> Kernel:你的手指触摸屏幕,触摸屏控制器检测手指的位置和压力,并将这些原始数据作为输入事件发送到 Linux 内核。
  2. Kernel -> InputReader:安卓系统的 InputReader 该进程持续监控内核的输入设备,它读取原始数据并将其转换为结构化的 Android 数据MotionEvent
  3. InputReader -> InputDispatcherInputDispatcher是所有输入信息的中央协调机构,它的主要任务是确定应该向哪个窗口接收这些MotionEvent信息,但在将事件发送到应用程序窗口之前,它会先咨询窗口管理器。

步骤 2:关键拦截点(窗口管理器服务)

这里系统手势指针事件监听器发挥作用。

  1. InputDispatcher -> WindowManagerService (WMS)InputDispatcher发送动作事件窗口管理服务 (WMS) 负责与窗口相关的一切:窗口的位置、大小以及当前聚焦的窗口。
  2. WMS 会查询PointerEventListeners。 WMS维护着一份特殊的、高优先级的监听者名单,这些监听者可以“偷看”所有内容,动作事件在它进入应用程序之前,SystemGesturesPointerEventListener是这些听众之一,已注册显示策略(一个管理系统 UI 策略的类)。

步骤 3:决策——系统手势还是应用程序手势?

这动作事件现已送达SystemGesturesPointerEventListener.onPointerEvent()您提供的代码开始执行。

场景 A:系统识别并“接收”手势

  1. 检测: 这检测滑动方法运行。它发现触摸操作始于fromY <= mSwipeStartThreshold.top并向下移动了足够远的距离(y > fromY + mSwipeDistanceThreshold它返回SWIPE_FROM_TOP
  2. 行动: 这onPointerEvent方法调用回调mCallbacks.onSwipeFromTop()
  3. 执行:显示策略实现此回调的函数接收到信号后,会通知系统 UI 展开通知面板。
  4. 消耗这是最关键的部分。因为SystemGesturesPointerEventListener它成功识别出系统手势,实际上就告诉输入系统:“我已经处理了这个事件。其他人不需要看到它。”InputDispatcher 接收到此信号并终止事件的旅程

结果: 这动作事件触摸操作永远不会传递给前台应用程序。屏幕上运行的游戏、浏览器或启动器完全不知道发生了触摸事件。


场景 B:系统无法识别手势

现在,想象一下你在屏幕中间滑动,远离任何边缘。

  1. 检测: 这MotionEvent仍然发送到SystemGesturesPointerEventListener.onPointerEvent()。 这检测滑动方法运行,但其所有功能都无法运行。如果条件失败,因为触摸并非从边缘附近开始。该方法返回SWIPE_NONE
  2. 无行动:未触发任何系统级回调。
  3. 途经监听器向输入系统发出信号:“我查看了这个事件,但它与我无关。你可以把它转交给其他人。”
  4. 发送至应用程序: 这InputDispatcher由于事件未被处理,程序继续执行其正常任务。它会识别触摸坐标处最顶层的可见应用程序窗口。
  5. 交付至应用程序: 这MotionEvent 最终被发送到应用程序进程。它通常通过以下路径进入应用程序的视图层级结构:
    • Activity.dispatchTouchEvent():该活动优先获得处理机会。
    • ViewGroup.onInterceptTouchEvent():它沿着布局树向下移动(例如,从一个布局树开始)。LinearLayout一个FrameLayout父布局可以“拦截”事件,以便在子布局(如按钮)看到滚动等手势之前对其进行处理。
    • View.onTouchEvent():最后,它到达了目标视图(例如,按钮、ImageView)。这时,应用程序自身的逻辑(例如)就会执行。点击监听器或自定义手势检测)运行。
http://www.dtcms.com/a/586607.html

相关文章:

  • 嘉兴公司网站制作怎么做网站站长
  • 【C++】内部类和组合类
  • MySQL的锁机制:从全局锁到行级锁的全面剖析
  • 北京品牌网站定制公司网络营销推广方案总结
  • 【开题答辩全过程】以 海水水质监测大数据分析为例,包含答辩的问题和答案
  • 自己怎么1做网站做爰网站视屏
  • wordpress技术博客主题昆明网站快照优化公司
  • SpringBoot面试题03-BeanFactory
  • 单位做网站需要多少钱wordpress进不去仪表盘
  • 滨州网站建设模板建设宁阳网站seo推广
  • 免费咨询律师在线微信嘉兴网站排名优化公司
  • 长兴住房和城乡建设局网站昆明网络公司收费标准
  • phpcmsv9网站建设入门教程禅城网站建设公司
  • 2025年市场上主流的22种物联网传感器类型
  • Java Stream流完全指南:从入门到精通
  • Optuna超参数调优图例解读之超参数重要性图
  • 怎么和网站建设公司签合同建一个自己用的网站要多少钱
  • 数字通信入门
  • computed计算属性
  • 无锡做网站设计ui培训心得
  • 游戏“二开”:在巨人的肩膀上创造新世界
  • 网站 用户体验的重要性wordpress有多个页脚
  • 网站开发与数据库高端网站建设与发展
  • PyQt5 QMultiMap完全指南:深入理解Qt的多值映射容器
  • 公司想制作网站黄埔做网站
  • 大功率绿电制氢电源装置研究
  • 做电影网站用什么程序个人做免费网页
  • 网站开发两端对齐底行左对齐安庆做网站电话
  • linux wordpress配置已收录的网站不好优化
  • 怎么制作自己的网页网站首页直播视频怎么下载