1. 诡异的Bug
在开发一款绘图类Qt应用时,我们遇到了一个非常严重的问题:
在鸿蒙设备上,当用户快速滑动屏幕时,笔画会出现断断续续的情况,而且笔画的位置似乎总是比手指实际触摸的位置偏下大约100像素。更糟糕的是,当三个手指同时按下时,应用直接忽略了第三个手指的事件。
这在Android和iOS上从未发生过。
2. 事件传递链分析
要解决这个问题,必须理解鸿蒙的输入事件是如何传递给Qt的。
在OpenHarmony中,UI结构通常是:
Window -> ArkUI Page -> XComponent -> Native Engine -> Qt Event Dispatcher。
问题的根源通常出现在 XC 到 CPP 这一跳,或者 CPP 到 QPA 的坐标转换上。
3. 问题剖析
Bug 1: 坐标偏移 (Y轴向下偏)
原因:
ArkUI的XComponent可以通过CSS设置位置。如果XComponent不是全屏的,或者上方有自定义的标题栏(TitleBar),XComponent的(0,0)坐标实际上是屏幕的(0, TitleBarHeight)。
然而,鸿蒙的触摸事件(TouchInfo)返回的坐标通常有两种:
localX/localY: 相对于组件的坐标。screenX/screenY: 相对于屏幕的坐标。
在早期的Qt适配代码中,可能错误地使用了screenY直接作为Qt窗口的坐标,而没有减去XComponent的偏移量;或者使用了localY但Qt窗口被认为是全屏的。
修复方案:
在NAPI接收Touch事件的回调中,必须确保使用相对于XComponent的局部坐标。
cpp
// NapiInput.cpp 伪代码
void OnTouchEvent(napi_env env, napi_value event) {
// 获取触摸点列表
// ...
float x = touchInfo.x; // 确保这是 localX
float y = touchInfo.y; // 确保这是 localY
// 如果使用了 screenX,必须手动减去窗口偏移
// float y = touchInfo.screenY - g_windowOffsetY;
// 发送给Qt
QWindowSystemInterface::handleTouchEvent(
g_qtWindow,
timestamp,
device,
points
);
}
Bug 2: 多点触控丢失
原因:
鸿蒙的XComponent默认可能只开启了单点触控,或者Qt侧的QTouchEvent处理逻辑有问题。
但在排查中发现,真正的原因是事件类型映射错误。
鸿蒙的触摸事件类型有:
DOWN(手指按下)UP(手指抬起)MOVE(移动)CANCEL(取消)
当第二个手指按下时,鸿蒙发出的可能是DOWN事件(但在多点触控标准中,后续手指通常对应POINTER_DOWN)。如果适配层代码只识别了主手指的DOWN,就会忽略后续手指。
修复方案:
我们需要完整处理所有触摸点的状态变化。
cpp
// 适配层关键逻辑
void HandleOhosTouchEvent(const OhosTouchEvent& event) {
QList<QTouchEvent::TouchPoint> touchPoints;
// 鸿蒙一次性返回所有当前活动的触摸点
for (const auto& point : event.points) {
QTouchEvent::TouchPoint tp;
tp.setId(point.id);
tp.setPos(QPointF(point.x, point.y));
// 关键:根据当前点状态映射Qt状态
switch (point.action) {
case OHOS_TOUCH_DOWN:
tp.setState(Qt::TouchPointPressed);
break;
case OHOS_TOUCH_MOVE:
tp.setState(Qt::TouchPointMoved);
break;
case OHOS_TOUCH_UP:
tp.setState(Qt::TouchPointReleased);
break;
default:
tp.setState(Qt::TouchPointStationary);
break;
}
touchPoints.append(tp);
}
// 注入Qt事件循环
QWindowSystemInterface::handleTouchEvent(
nullptr, // 默认窗口
event.timestamp,
g_touchDevice,
touchPoints
);
}
Bug 3: 笔画断断续续(事件频率问题)
原因:
ArkUI的事件回调频率非常高(可能达到120Hz),而Qt的主线程如果负载过重,可能处理不过来,导致事件堆积或丢弃。
另一个原因是XComponent的渲染模式配置。
修复方案:
- 开启批量处理 :不要每收到一个点就调用一次
QWindowSystemInterface。可以将短时间内的点合并。 - XComponent配置 :在ArkTS侧,确保XComponent的类型设置为
SURFACE,并且正确配置了libraryname。
typescript
// Index.ets
XComponent({
id: 'qt_xcomponent',
type: 'surface',
libraryname: 'qtapp'
})
.onTouch((event: TouchEvent) => {
// 这里的JS回调可能会有性能瓶颈
// 最佳实践是直接让XComponent内部的Native层处理输入,
// 而不是通过JS回调转发(如果支持的话)
})
注:最新的Qt for OpenHarmony版本通常直接在Native层对接Multimodal Input服务,绕过了JS层的性能瓶颈。如果你的版本还在用JS转发Touch事件,建议立刻升级。
4. 验证工具
为了验证修复效果,我们编写了一个简单的"多点触控测试仪"(Qt Widget):
cpp
void TouchTester::touchEvent(QTouchEvent *event) {
const auto &points = event->touchPoints();
QString log;
for (const auto &p : points) {
log += QString("ID:%1 [%2,%3] State:%4 | ")
.arg(p.id())
.arg(p.pos().x(), 0, 'f', 1)
.arg(p.pos().y(), 0, 'f', 1)
.arg(p.state());
}
qDebug() << log;
update(); // 触发重绘显示触点
}
5. 总结
触控体验是移动应用的生命线。在鸿蒙平台上,由于存在ArkUI和Qt两套坐标系和事件系统,适配工作极其繁琐。
- 坐标系对齐:永远使用相对于控件的局部坐标,或者准确计算全局偏移。
- 多指状态映射 :正确处理
POINTER_DOWN/UP,不要遗漏非主手指的状态。 - 性能优化:尽量下沉到C++ Native层处理Input事件,避免经过ArkTS JS层的转发开销。
解决这些问题后,Qt应用在鸿蒙上的手感将能媲美原生应用。