10、触摸与事件同步:多点触控事件转发、HID协议模拟、坐标映射与校准、手势识别与回传
触摸这件事,在车机互联里其实比你想的要复杂得多。
我刚开始做这个模块的时候,以为不就是把手机上的触摸事件传给车机嘛,有什么难的?结果第一次联调,手指在手机上划一下,车机上的反应延迟了快半秒,而且坐标完全对不上。嗯,那场面,挺尴尬的。
这一章,我们就来聊聊触摸与事件同步的那些坑。说白了,就是解决三个问题:怎么传、怎么对准、怎么识别手势。
10.1 多点触控事件转发:从手机到车机的“快递”
手机上的触摸事件,本质上是 Linux 输入子系统上报的。Android 的 InputReader 读取这些事件,然后分发给应用层。但在车机互联场景下,我们需要把这些事件“截胡”,然后转发给车机。
事件转发的核心流程:
- 捕获事件:在手机端,通过 AccessibilityService 或直接读取 /dev/input/eventX 设备节点,拿到原始的触摸事件。
- 序列化:将事件结构体(struct input_event)打包成字节流。每个事件包含 type、code、value 三个字段。
- 传输:通过 TCP/UDP 或蓝牙 RFCOMM 发送给车机端。
- 反序列化:车机端收到字节流后,还原成 input_event 结构。
- 注入:通过 /dev/uinput 或 InputManager 的 injectInputEvent 方法,将事件注入到车机的输入系统中。
关键点:多点触控使用的是 ABS_MT_* 系列事件,包括 ABS_MT_SLOT、ABS_MT_POSITION_X、ABS_MT_POSITION_Y、ABS_MT_TRACKING_ID 等。每个手指对应一个 slot,必须按顺序上报。
我在项目中遇到过一个问题:手机上报事件的速度是 120Hz,但车机的屏幕刷新率只有 60Hz。如果全量转发,车机根本处理不过来,反而导致事件堆积。后来我加了一个事件节流机制,每 16ms 只转发最新的一组事件,问题就解决了。
10.2 HID协议模拟:让车机“以为”是本地触摸
有些车机系统比较封闭,不允许直接注入事件。这时候就需要用 HID 协议来模拟一个触摸屏设备。
HID(Human Interface Device)协议是 USB 和蓝牙设备的标准协议。我们可以在车机上创建一个 HID 设备,然后通过 HID Report Descriptor 描述触摸屏的能力。
HID 触摸屏的 Report Descriptor 示例:
// 一个简单的多点触控 HID 描述符
0x05, 0x0D, // Usage Page (Digitizers)
0x09, 0x04, // Usage (Touch Screen)
0xA1, 0x01, // Collection (Application)
0x85, 0x01, // Report ID (1)
0x09, 0x22, // Usage (Finger)
0xA1, 0x02, // Collection (Logical)
0x09, 0x42, // Usage (Tip Switch)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x01, // Report Count (1)
0x81, 0x02, // Input (Data,Var,Abs)
0x09, 0x32, // Usage (In Range)
0x81, 0x02, // Input (Data,Var,Abs)
0x95, 0x06, // Report Count (6)
0x81, 0x03, // Input (Const,Var,Abs)
0x09, 0x30, // Usage (Tip X)
0x09, 0x31, // Usage (Tip Y)
0x16, 0x00, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x7F, // Logical Maximum (32767)
0x75, 0x10, // Report Size (16)
0x95, 0x02, // Report Count (2)
0x81, 0x02, // Input (Data,Var,Abs)
0xC0, // End Collection
0xC0 // End Collection
你想想看,车机收到这个描述符后,就会认为有一个 USB 触摸屏接入了。然后我们只需要按照这个格式发送 HID Report 数据,车机就会自动解析成触摸事件。
注意:HID 协议模拟需要车机支持 USB Gadget 或蓝牙 HID Profile。有些老款车机不支持,那就只能走传统的事件注入方式。
10.3 坐标映射与校准:别让手指“跑偏”
手机屏幕和车机屏幕的尺寸、分辨率、DPI 都不一样。如果不做坐标映射,你在手机左上角点一下,车机上可能点到了右下角。
坐标映射的核心公式:
// 假设手机分辨率是 phoneW x phoneH,车机分辨率是 carW x carH
// 触摸点在手机上的坐标是 (touchX, touchY)
// 映射到车机上的坐标是 (mappedX, mappedY)
mappedX = touchX * (carW / phoneW)
mappedY = touchY * (carH / phoneH)
这个公式看起来简单,但实际项目中会遇到几个问题:
- 比例不一致:手机是 20:9,车机是 16:9。直接等比缩放会导致画面拉伸或黑边。这时候需要做裁剪映射,或者保持比例并居中显示。
- 偏移校准:有些车机的触摸屏本身就有硬件偏移。我曾经遇到一台车,触摸屏的物理坐标和显示坐标差了 5 个像素。解决办法是在车机上做一个四点校准,计算出一个仿射变换矩阵。
- 旋转适配:手机横屏时,触摸坐标需要旋转 90 度才能对应车机的竖屏显示。
我的经验:坐标映射最好在车机端做,因为车机知道自己的屏幕参数。手机端只负责上报原始坐标,这样更灵活。
10.4 手势识别与回传:从“点按”到“滑动”
触摸事件不只是点按,还有滑动、长按、双指缩放等手势。如果每个手势都传原始事件,车机端的处理逻辑会非常复杂。所以,我们可以在手机端做一些手势识别,然后把识别结果回传给车机。
常见的手势类型:
| 手势 | 触发条件 | 回传数据 |
|---|---|---|
| 单击 | 手指按下后快速抬起,移动距离 < 10px | ACTION_CLICK, (x, y) |
| 双击 | 两次单击间隔 < 300ms | ACTION_DOUBLE_CLICK, (x, y) |
| 滑动 | 手指按下后移动距离 > 50px | ACTION_SWIPE, direction, distance |
| 双指缩放 | 两个手指同时按下并移动 | ACTION_PINCH, scaleFactor |
| 长按 | 手指按下超过 500ms 且移动 < 10px | ACTION_LONG_PRESS, (x, y) |
手势识别的算法其实不复杂。我一般用状态机来实现:
// 手势识别状态机伪代码
enum GestureState {
IDLE,
TOUCH_DOWN,
TOUCH_MOVE,
TOUCH_UP
}
GestureState state = IDLE;
Point downPoint;
long downTime;
void onTouchEvent(TouchEvent event) {
switch (state) {
case IDLE:
if (event.action == DOWN) {
state = TOUCH_DOWN;
downPoint = event.point;
downTime = System.currentTimeMillis();
}
break;
case TOUCH_DOWN:
if (event.action == MOVE) {
float distance = distance(downPoint, event.point);
if (distance > THRESHOLD_MOVE) {
// 识别为滑动
sendGesture(SWIPE, direction(downPoint, event.point));
state = IDLE;
}
} else if (event.action == UP) {
long duration = System.currentTimeMillis() - downTime;
if (duration < THRESHOLD_LONG_PRESS) {
// 识别为单击
sendGesture(CLICK, event.point);
} else {
// 识别为长按
sendGesture(LONG_PRESS, event.point);
}
state = IDLE;
}
break;
}
}
为什么要做手势回传?说白了,就是为了减少车机端的计算压力。车机端的 CPU 通常不如手机强,而且还要跑导航、音乐等应用。把手势识别放在手机端,车机只需要执行对应的 UI 操作就行了。
避坑指南:我曾经把双指缩放的识别阈值设得太小,导致用户在滑动列表时经常误触发放大。后来我把阈值从 20px 调整到 50px,误触率就降下来了。嗯,这个阈值需要根据实际场景调优。
知识体系总览
下面这张图总结了触摸与事件同步的核心模块和它们之间的关系:
从这张图可以看出来,整个触摸同步链路是双向的。手机端负责捕获和识别,车机端负责映射和注入。中间通过传输协议连接。HID 模拟是备选方案,当车机不允许直接注入事件时使用。
好了,这一章的内容就到这里。触摸与事件同步是车机互联体验的关键,做得好,用户感觉就像在操作本地屏幕一样流畅。做得不好,那就是灾难。