You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从/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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 06:23:19