2. 嵌入式触摸系统概述
触摸屏开发,手势识别是绕不开的核心环节。很多刚入行的朋友觉得手势不就是“点一下、划一下”嘛,真做起来才发现坑不少。我最早做工业平板项目时,就因为在手势识别上偷了懒,结果现场操作员反馈“点十次有三次没反应”——那叫一个尴尬。
这一章,咱们把Qt里的触摸手势体系彻底捋一遍。从最底层的单点触摸事件,到系统级的多点触摸,再到QGesture框架,最后手撸一个自定义手势。你跟着走一遍,以后遇到手势相关的问题心里就有底了。
2.1 单点触摸:最基础也最容易出错
单点触摸,说白了就是“一根手指”的操作。Qt里处理单点触摸,核心是重写 QWidget::touchEvent() 或者 QQuickItem::touchEvent()。
但这里有个常见的误区:很多人以为 mousePressEvent 就能搞定触摸。嗯,在电阻屏时代确实可以,但电容屏的触摸事件和鼠标事件是两条独立的路径。如果你只处理鼠标事件,在纯触摸设备上可能会漏掉一些触摸特有的属性,比如触摸压力、触摸面积。
touchEvent 和 mouseEvent,但以 touchEvent 为主。因为触摸事件能拿到更精确的 QTouchEvent::TouchPoint 信息。
来看一个最简单的单点触摸示例:
// 在QWidget子类中启用触摸
setAttribute(Qt::WA_AcceptTouchEvents);
// 重写touchEvent
bool MyWidget::event(QEvent *event)
{
if (event->type() == QEvent::TouchBegin ||
event->type() == QEvent::TouchUpdate ||
event->type() == QEvent::TouchEnd) {
QTouchEvent *touchEvent = static_cast<QTouchEvent*>(event);
const QList<QTouchEvent::TouchPoint> &points = touchEvent->touchPoints();
if (points.size() == 1) {
// 单点触摸处理
const QTouchEvent::TouchPoint &pt = points.first();
qDebug() << "触摸位置:" << pt.pos() << "压力:" << pt.pressure();
}
return true;
}
return QWidget::event(event);
}
QPA_EGLFS_INTEGRATION 或者 QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS,触摸事件可能根本不会触发。调试时先确认 QT_DEBUG_PLUGINS=1 环境变量下能看到触摸插件加载成功。
2.2 多点触摸:两根手指的世界
多点触摸,最常见的就是双指缩放、双指旋转。Qt对多点触摸的支持,底层依赖 QTouchEvent 中的 TouchPoint 列表。每个手指对应一个 TouchPoint,它们有独立的ID、位置、压力、面积。
你想想看,如果要做双指缩放,核心逻辑就是计算两个手指之间的距离变化。我见过有人用 QPinchGesture 来做,但有时候手势识别不够灵敏,不如自己算距离来得直接。
| 触摸点数 | 常见手势 | Qt推荐处理方式 |
|---|---|---|
| 1 | 点击、拖动、长按 | QTouchEvent + 自定义逻辑 |
| 2 | 缩放、旋转、双指滑动 | QPinchGesture / 手动计算距离 |
| 3+ | 三指切换、四指任务 | QTouchEvent + 手指ID追踪 |
下面是一个手动实现双指缩放的片段:
void MyView::touchEvent(QTouchEvent *event)
{
const auto &points = event->touchPoints();
if (points.size() == 2) {
QPointF p1 = points[0].pos();
QPointF p2 = points[1].pos();
qreal currentDist = QLineF(p1, p2).length();
if (event->type() == QEvent::TouchBegin) {
m_lastDist = currentDist;
} else if (event->type() == QEvent::TouchUpdate) {
qreal scale = currentDist / m_lastDist;
// 应用缩放
applyZoom(scale);
m_lastDist = currentDist;
}
}
}
QTouchEvent::TouchPoint::id() 来建立手指到逻辑点的映射。
2.3 QGesture手势识别框架
Qt提供了一套高级手势识别框架——QGesture。它把底层的触摸事件封装成了更语义化的手势:捏合、旋转、滑动、轻扫。你不需要自己算距离、算角度,框架帮你做了。
使用QGesture的步骤很简单:
- 在目标widget上调用
grabGesture()注册手势类型 - 重写
event()或gestureEvent()处理QGestureEvent - 从
QGesture子类中提取手势参数
举个例子,注册并处理捏合缩放:
// 在构造函数中
grabGesture(Qt::PinchGesture);
// 重写event
bool MyWidget::event(QEvent *event)
{
if (event->type() == QEvent::Gesture) {
QGestureEvent *ge = static_cast<QGestureEvent*>(event);
if (QGesture *pinch = ge->gesture(Qt::PinchGesture)) {
QPinchGesture *pg = static_cast<QPinchGesture*>(pinch);
qreal scaleFactor = pg->scaleFactor();
qreal rotationAngle = pg->rotationAngle();
// 应用缩放和旋转
return true;
}
}
return QWidget::event(event);
}
2.4 自定义手势:从零开始造轮子
为什么需要自定义手势?因为实际项目里总有奇奇怪怪的需求。我记得有个医疗设备项目,客户要求“在屏幕上画一个‘L’形来启动校准”。这种手势QGesture肯定不支持,只能自己写。
自定义手势的核心思路:
- 采集轨迹:在
touchEvent中记录手指移动的点序列 - 特征提取:计算方向、速度、曲率、拐点
- 模板匹配:与预定义的手势模板做相似度比较
- 触发动作:匹配成功则执行对应操作
下面是一个简单的“画圈”手势识别器骨架:
class CircleGestureRecognizer {
public:
bool recognize(const QVector<QPointF> &points) {
if (points.size() < 20) return false; // 点数太少
// 1. 计算中心点
QPointF center;
for (auto &p : points) center += p;
center /= points.size();
// 2. 计算每个点到中心的距离方差
qreal avgDist = 0;
for (auto &p : points) {
avgDist += QLineF(p, center).length();
}
avgDist /= points.size();
qreal variance = 0;
for (auto &p : points) {
qreal d = QLineF(p, center).length();
variance += (d - avgDist) * (d - avgDist);
}
variance /= points.size();
// 3. 如果方差很小,说明轨迹接近圆形
return variance < 50.0; // 阈值需根据实际调整
}
};
2.5 手势识别架构总览
为了让你对整个体系有个直观认识,我画了一张结构图。它展示了从底层触摸事件到上层手势识别的完整链路。
2.6 手势识别的性能调优
手势识别做不好,最直接的表现就是“卡顿”或“误触”。我总结了几条实战经验:
- 减少事件处理中的计算量:不要在
touchEvent里做复杂的数学运算,尤其是浮点运算。把特征提取放到单独的线程或延迟处理。 - 合理设置采样率:不是采样率越高越好。60fps的触摸采样率已经足够,过高的采样率只会增加CPU负担。
- 使用QGesture的缓存机制:QGesture内部有状态缓存,不要重复创建QGesture对象。
- 避免在手势处理中触发重绘:手势识别过程中尽量减少
update()调用,可以用定时器合并重绘请求。
2.7 调试技巧:让手势问题无处遁形
手势调试比普通UI调试更麻烦,因为它是“动态的”。我常用的调试手段:
- 可视化轨迹:在屏幕上实时绘制触摸点的移动轨迹,颜色随压力变化。这样一眼就能看出触摸是否准确。
- 日志打点:在
TouchBegin、TouchUpdate、TouchEnd三个关键点打印时间戳,计算事件间隔是否均匀。 - 使用Qt的触摸模拟器:在PC上开发时,可以用
QTouchDevice模拟触摸事件,不用每次都烧写到板子上。
// 可视化轨迹的简单实现
void MyWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
// 绘制触摸轨迹
painter.setPen(QPen(Qt::blue, 3));
for (int i = 1; i < m_touchPoints.size(); ++i) {
painter.drawLine(m_touchPoints[i-1], m_touchPoints[i]);
}
// 绘制触摸点
for (auto &pt : m_touchPoints) {
qreal radius = pt.pressure() * 20; // 压力越大,点越大
painter.setBrush(QColor(255, 0, 0, 128));
painter.drawEllipse(pt.pos(), radius, radius);
}
}
嗯,手势识别这块内容就这些。从单点触摸到自定义手势,每个层次都有它的适用场景。你在实际项目中,可以根据硬件能力和交互复杂度,选择最合适的方案。记住一点:手势识别不是越复杂越好,稳定、可预测才是第一位的。