2. 触摸事件处理机制:TouchGFX中触摸事件的产生、分发与消费流程详解

触摸响应快不快,说白了就看事件从手指到屏幕反馈走了多远的路。我刚开始用TouchGFX时,总觉得点一下按钮没反应,后来一查才发现事件在中间绕了一大圈。今天我们就把它拆开看看。

2.1 触摸事件的源头:硬件中断到驱动层

手指按下触摸屏,最先干活的是硬件。触摸控制器(比如FT5x06、GT911)检测到电容变化,通过I2C或SPI把坐标数据塞进寄存器,然后拉高中断引脚通知MCU。

MCU的中断服务函数里,驱动层会做三件事:

  1. 读取触摸坐标和触摸状态(按下/抬起/滑动)
  2. 将原始数据转换成屏幕坐标系下的像素坐标
  3. 调用 TouchController::sampleTouch() 把数据喂给TouchGFX

关键点:驱动层的采样频率直接影响触摸响应延迟。我见过一个项目,驱动里加了10ms的延时滤波,结果触摸总感觉慢半拍。去掉后响应立刻跟手了。

2.2 事件分发核心:HAL层到框架层

TouchGFX的 HAL::touchSample() 会轮询驱动层拿到的坐标数据。拿到后,它不直接丢给控件,而是先做两件事:

  • 坐标变换:如果屏幕旋转了,这里会做矩阵变换
  • 多点去抖:连续两次采样坐标差小于阈值,认为是抖动,丢弃

处理完的数据被包装成 TouchEvent 对象,包含:

字段 说明
type 事件类型:TOUCH_DOWN / TOUCH_UP / TOUCH_MOVE / TOUCH_CANCEL
x, y 触摸点在屏幕上的像素坐标
velocity 滑动速度(用于惯性滚动等场景)

我的习惯:在 HAL::touchSample() 里加个GPIO翻转,用示波器看中断到事件入队的延迟。如果超过2ms,就得优化驱动了。

2.3 事件消费流程:谁抢到了谁处理

TouchGFX的事件消费遵循「从顶到底,先到先得」的原则。流程是这样的:

  1. Screen 收到 TouchEvent
  2. 遍历所有 Container,检查触摸点是否在控件区域内
  3. 如果某个控件 isTouchable() 返回 true,且坐标在它的 getRect() 内,就调用它的 handleTouchEvent()
  4. 如果控件消费了事件(返回 true),分发停止;否则继续往下找

这里有个坑:Z序。后添加的控件在Z序上层,会优先收到事件。我曾经遇到一个透明覆盖层挡住了所有按钮,排查了半天才发现是Z序问题。

我曾经踩过的坑:在 handleClickEvent() 里做了耗时操作(比如读写Flash),导致触摸事件队列阻塞。手指抬起了,事件还没处理完,界面卡死。解决方案是把耗时操作扔到 Application::postDelayedTask() 里异步执行。

2.4 事件流程图:从手指到回调

下面这张图把整个流程串起来了。我建议你把它打印出来贴在工位上。

TouchGFX触摸事件处理流程 TouchGFX 触摸事件处理流程 硬件中断触发 驱动层采样 + 坐标转换 HAL层去抖 + 事件包装 Screen 遍历子控件 命中检测 + Z序判断 handleTouchEvent() 回调 说明: 如果控件消费了事件(返回true),分发立即停止;否则继续遍历下一个控件。

2.5 消费规则详解:谁可以吃掉事件

一个控件要消费触摸事件,必须同时满足三个条件:

  • 可见且启用:isVisible() == true 且 isTouchable() == true
  • 坐标命中:触摸点在 getRect() 范围内(考虑父容器的裁剪区域)
  • 未被上层控件拦截:Z序更高的控件没有先吃掉事件

你想想看,如果一个按钮被一个透明的 Box 盖住了,就算按钮的坐标命中逻辑是对的,事件也到不了它手里。这就是为什么我总强调:调试触摸问题先看Z序。

避坑指南:我曾经在 CustomContainer 里重写了 handleTouchEvent(),但忘了调用基类的 handleTouchEvent(),结果子控件永远收不到事件。嗯,这个坑我踩了两次才记住。

2.6 多点触摸与手势识别

TouchGFX原生支持最多5点触摸。每个触摸点独立走上面的流程,但 Screen 会维护一个 touchPoints 列表,记录所有活跃的触摸点。

手势识别(比如滑动、缩放)是在 Screen 层做的。它监听 TOUCH_MOVE 事件,计算两个触摸点之间的距离变化,然后触发 GestureEvent。我建议你在 handleGestureEvent() 里处理手势,而不是在 handleTouchEvent() 里自己算,因为框架已经帮你做了滤波和阈值判断。

手势类型 触发条件 常用场景
SWIPE_HORIZONTAL 水平滑动距离 > 30px,速度 > 200px/s 页面左右切换
SWIPE_VERTICAL 垂直滑动距离 > 30px,速度 > 200px/s 列表上下滚动
PINCH 双指间距变化 > 10% 图片缩放

我的经验:手势识别的阈值要根据屏幕尺寸和用户群体调整。工业屏用户戴手套操作,滑动速度会偏慢,我把阈值从200px/s降到了120px/s,体验好了很多。

2.7 性能优化:减少事件传递路径长度

事件从产生到消费,路径越长延迟越大。我总结了几条优化原则:

  • 减少嵌套层级:Container 嵌套不要超过5层,每层遍历都有开销
  • 避免在事件回调中做重活:比如读写文件、网络请求,扔到后台任务里
  • 合理使用 setTouchable(false):不需要触摸的控件关掉触摸检测,减少遍历次数
  • 批量处理 MOVE 事件:如果每秒产生60个 MOVE 事件,没必要每个都触发重绘,可以合并成每16ms处理一次

嗯,这一章的内容就到这里。触摸事件机制是TouchGFX性能优化的基石,搞懂了它,后面讲响应延迟优化、帧率调优你才能听得明白。