如何从/dev/input/event0获取Android多点触摸的精准坐标?
问题:Android自定义输入事件处理在多点触摸屏幕失效,坐标数值超出屏幕分辨率
我在Android平台上不使用Eventhub和InputReader,自行编写程序处理输入事件,该代码在单点触摸屏幕上可正常运行,但在多点触摸屏幕上失效。以下是我的部分代码:
int readSize = read(mInputTouchfd, mEventData, EVS_READ_EVENT_MAX_NUM * sizeof(struct input_event)); if (readSize >= (int)sizeof(struct input_event)) { int eventNum = readSize / sizeof(struct input_event); LOG1("HandleTouchEvent has read %d input event", eventNum); for (int i = 0; i < eventNum; i++) { LOG1("number %d event,details:type:[0x%X] code:[0x%X (%d)] value:[0x%X]", i, mEventData[i].type, mEventData[i].code, mEventData[i].code, mEventData[i].value); if (mEventData[i].type == EV_ABS && mEventData[i].code == ABS_MT_POSITION_X) { realx = mEventData[i].value; } else if (mEventData[i].type == EV_ABS && mEventData[i].code == ABS_MT_POSITION_Y) { realy = mEventData[i].value; } else if (mEventData[i].type == EV_KEY && mEventData[i].code == BTN_TOUCH) { if (mEventData[i].value == TOUCH_EVENT_DOWN) { buttonEvent = TOUCH_EVENT_DOWN; } else if (mEventData[i].value == TOUCH_EVENT_UP) { buttonEvent = TOUCH_EVENT_UP; } } else if (mEventData[i].type == EV_SYN && mEventData[i].code == SYN_REPORT) { LOG1("parse Event succeed:Event[%s],x[%d],y[%d]", toString(buttonEvent), realx, realy); if (spHomePage) { spHomePage->dispatchTouchEvent(buttonEvent, realx, realy); } // reset event type after handle finished buttonEvent = TOUCH_EVENT_MOVE; } } }
使用getevent命令捕获到多点触摸屏幕的相关事件:
/dev/input/event0: EV_KEY BTN_TOUCH UP /dev/input/event0: EV_MSC MSC_TIMESTAMP 00032c80 /dev/input/event0: EV_SYN SYN_REPORT 00000000 /dev/input/event0: EV_ABS ABS_MT_TRACKING_ID 0000004d /dev/input/event0: EV_ABS ABS_MT_POSITION_X 00003dad /dev/input/event0: EV_ABS ABS_MT_POSITION_Y 000012bc /dev/input/event0: EV_KEY BTN_TOUCH DOWN /dev/input/event0: EV_ABS ABS_X 00003dad /dev/input/event0: EV_ABS ABS_Y 000012bc /dev/input/event0: EV_MSC MSC_TIMESTAMP 00034bc0 /dev/input/event0: EV_SYN SYN_REPORT 00000000 /dev/input/event0: EV_MSC MSC_TIMESTAMP 00036b00
发现当type为EV_ABS、code为ABS_MT_POSITION_X时,值为十六进制00003dad(十进制15789),远大于我的10英寸16:9 1080p屏幕分辨率1920。不清楚如何获取精准的X/Y坐标,原以为数值单位是像素但实际并非如此,恳请帮助。
解决方案
1. 处理多点触摸的核心:ABS_MT_TRACKING_ID
你的代码未处理ABS_MT_TRACKING_ID,这是区分多点触摸不同手指的唯一标识。每个触摸点在事件序列中都会携带对应的Tracking ID,直到该触摸点抬起(此时ID值为-1)。
需要维护一个以Tracking ID为键的哈希表,存储每个触摸点的坐标和状态:
- 收到
ABS_MT_TRACKING_ID时,若值为-1则移除对应触摸点;若为有效ID则初始化或更新该点数据 - 每组事件以
SYN_REPORT结尾,此时所有触摸点的信息已完整,可统一处理
2. 坐标转换:从硬件原始值到屏幕像素
ABS_MT_POSITION_X/Y的数值是触摸硬件的原始采样值,并非直接像素。需先读取输入设备的ABS轴范围,再通过比例转换为屏幕像素。
读取ABS轴范围
使用ioctl调用EVIOCGABS获取轴的范围信息:
#include <linux/input.h> struct input_absinfo abs_x_info, abs_y_info; // 读取X轴原始值范围 ioctl(mInputTouchfd, EVIOCGABS(ABS_MT_POSITION_X), &abs_x_info); // 读取Y轴原始值范围 ioctl(mInputTouchfd, EVIOCGABS(ABS_MT_POSITION_Y), &abs_y_info);
input_absinfo结构体的minimum和maximum字段对应该轴的原始值上下限。
转换为屏幕像素
假设屏幕分辨率为SCREEN_WIDTH=1920、SCREEN_HEIGHT=1080,转换公式如下:
// X坐标转换:(原始值 - 最小值) / (最大值 - 最小值) * 屏幕宽度 int screen_x = (raw_x - abs_x_info.minimum) * SCREEN_WIDTH / (abs_x_info.maximum - abs_x_info.minimum); // Y坐标转换:部分设备Y轴原始值方向与屏幕相反,需反转 int screen_y = (abs_y_info.maximum - raw_y) * SCREEN_HEIGHT / (abs_y_info.maximum - abs_y_info.minimum);
3. 修正后的事件处理逻辑示例
#include <linux/input.h> #include <unordered_map> // 存储单个触摸点数据 struct TouchPoint { int raw_x; int raw_y; int state; // TOUCH_EVENT_DOWN/UP/MOVE }; std::unordered_map<int, TouchPoint> touch_points; struct input_absinfo abs_x_info, abs_y_info; const int SCREEN_WIDTH = 1920; const int SCREEN_HEIGHT = 1080; // 初始化时读取ABS轴范围 void initTouchDevice(int fd) { ioctl(fd, EVIOCGABS(ABS_MT_POSITION_X), &abs_x_info); ioctl(fd, EVIOCGABS(ABS_MT_POSITION_Y), &abs_y_info); } void HandleTouchEvent() { int readSize = read(mInputTouchfd, mEventData, EVS_READ_EVENT_MAX_NUM * sizeof(struct input_event)); if (readSize >= (int)sizeof(struct input_event)) { int eventNum = readSize / sizeof(struct input_event); LOG1("HandleTouchEvent has read %d input event", eventNum); TouchPoint current_point; int current_tracking_id = -1; for (int i = 0; i < eventNum; i++) { struct input_event* event = &mEventData[i]; switch(event->type) { case EV_ABS: switch(event->code) { case ABS_MT_TRACKING_ID: current_tracking_id = event->value; if (current_tracking_id == -1) { // 触摸点抬起,移除记录 touch_points.erase(current_tracking_id); } else { // 获取或初始化当前触摸点 current_point = touch_points[current_tracking_id]; } break; case ABS_MT_POSITION_X: current_point.raw_x = event->value; break; case ABS_MT_POSITION_Y: current_point.raw_y = event->value; break; case ABS_X: // 兼容单点触摸的ABS_X/Y事件 current_point.raw_x = event->value; break; case ABS_Y: current_point.raw_y = event->value; break; } break; case EV_KEY: if (event->code == BTN_TOUCH) { current_point.state = (event->value == TOUCH_EVENT_DOWN) ? TOUCH_EVENT_DOWN : TOUCH_EVENT_UP; } break; case EV_SYN: if (event->code == SYN_REPORT) { if (current_tracking_id != -1) { // 更新触摸点数据 touch_points[current_tracking_id] = current_point; // 转换为屏幕像素坐标 int screen_x = (current_point.raw_x - abs_x_info.minimum) * SCREEN_WIDTH / (abs_x_info.maximum - abs_x_info.minimum); int screen_y = (abs_y_info.maximum - current_point.raw_y) * SCREEN_HEIGHT / (abs_y_info.maximum - abs_y_info.minimum); LOG1("TrackingID[%d], Event[%s], ScreenX[%d], ScreenY[%d]", current_tracking_id, toString(current_point.state), screen_x, screen_y); if (spHomePage) { // 需修改dispatchTouchEvent以支持多点触摸,携带TrackingID spHomePage->dispatchTouchEvent(current_point.state, screen_x, screen_y, current_tracking_id); } } // 重置临时变量 current_tracking_id = -1; current_point.state = TOUCH_EVENT_MOVE; } break; } } } }
关键注意事项
SYN_REPORT是一组触摸事件的结束标记,此时所有触摸点的信息已完整,需统一处理- 部分设备会同时发送ABS_X/Y和ABS_MT_POSITION_X/Y事件,需兼容两种情况
- 必须处理
ABS_MT_TRACKING_ID的-1值,避免残留无效触摸点数据
内容的提问来源于stack exchange,提问作者FranzKafka Yu
相关产品推荐
相关产品推荐

