6.1 process()函数:入口与流程
每个Mapper的灵魂都在process()函数里。SingleTouchMapper也不例外。当InputReader从设备节点读到原始数据后,就会调用这个函数。
void SingleTouchMapper::process(const RawEvent* rawEvent) { // 1. 更新原始数据 updateTouchState(rawEvent); // 2. 检查是否有有效触摸 if (!mCurrentTouch.present) { return; } // 3. 坐标转换 int32_t x = mCurrentTouch.x; int32_t y = mCurrentTouch.y; transformTouch(&x, &y); // 4. 封装事件并上报 dispatchTouch(x, y, mCurrentTouch.pressure); }嗯,这里要注意,代码是我简化过的,但核心逻辑就这四步。我在项目中遇到过一些奇怪的触摸问题,最后发现都是因为第二步的检查没做好——明明手指已经离开了,但驱动还在上报无效数据。
6.2 坐标转换:从硬件到屏幕的魔法
坐标转换这块,是很多初学者容易懵的地方。你想想,触摸屏的物理坐标和屏幕的显示坐标,它们之间可不是简单的1:1关系。
举个例子,我手头有一块800x480的屏幕,但触摸屏的物理分辨率可能是1024x600。这时候就需要做映射。SingleTouchMapper里是怎么做的呢?
void SingleTouchMapper::transformTouch(int32_t* x, int32_t* y) { // 获取屏幕尺寸 int32_t screenWidth = mViewport.width; int32_t screenHeight = mViewport.height; // 获取触摸屏物理尺寸 int32_t touchWidth = mTouchDevice->getTouchWidth(); int32_t touchHeight = mTouchDevice->getTouchHeight(); // 线性映射 *x = (*x) * screenWidth / touchWidth; *y = (*y) * screenHeight / touchHeight; }说白了,这就是一个简单的比例缩放。但实际项目中,事情往往没那么简单。
我曾经踩过的坑:有一次,某款设备的触摸屏和屏幕的宽高比不一致。直接做线性映射,结果触摸点总是偏移。后来我查了驱动文档才发现,需要做裁剪处理——先按比例缩放,然后取交集区域。这个坑让我折腾了两天。
6.3 事件封装:从RawEvent到MotionEvent
坐标转换完成后,下一步就是封装事件。SingleTouchMapper需要把原始数据包装成MotionEvent,这样才能被上层使用。
这里有个关键点:事件类型。单点触摸的事件类型主要有三种:
| 事件类型 | 触发条件 | 说明 |
|---|---|---|
| ACTION_DOWN | 手指按下 | 触摸开始,只有一个触点 |
| ACTION_MOVE | 手指滑动 | 触点位置发生变化 |
| ACTION_UP | 手指抬起 | 触摸结束 |
封装的过程,其实就是把这些信息填到MotionEvent的结构体里。我习惯用下面这个伪代码来理解:
void SingleTouchMapper::dispatchTouch(int32_t x, int32_t y, float pressure) { // 创建MotionEvent对象 MotionEvent* event = new MotionEvent(); // 设置事件属性 event->setAction(getActionType()); // DOWN/MOVE/UP event->setPointerCount(1); // 单点触摸 event->setPointerId(0, 0); // 指针ID为0 event->setCoords(0, x, y); // 转换后的坐标 event->setPressure(0, pressure); // 压力值 // 上报给InputReader mListener->notifyMotion(event); }核心要点:SingleTouchMapper的process()函数,本质上就是一个“翻译官”。它把驱动层的原始语言(RawEvent),翻译成应用层能理解的语言(MotionEvent)。坐标转换是翻译过程中的关键步骤,而事件封装则是最终的输出格式。
6.4 避坑指南:我踩过的那些雷
做Android系统这么多年,SingleTouchMapper这块我遇到过不少问题。挑几个典型的说说:
- 坐标翻转问题:有些设备的触摸屏是倒着装上去的。这时候x和y轴需要做翻转。我曾经遇到过一款平板,触摸屏的x轴和屏幕的x轴方向相反,结果触摸点总是镜像的。解决方案是在
transformTouch()里加一个方向判断。 - 压力值异常:有些低端触摸屏,压力值永远返回0。这时候如果上层代码依赖压力值做判断,就会出问题。我的建议是:在Mapper层做一次过滤,如果压力值一直为0,就强制设为1.0。
- 多点误触:虽然叫SingleTouchMapper,但有些设备在物理上支持多点,只是驱动层把它模拟成了单点。这时候如果用户同时触摸两个点,驱动可能会上报混乱的数据。嗯,这种情况我一般建议升级到MultiTouchMapper。
个人经验:调试触摸问题时,我建议先在process()函数里加日志,打印出原始坐标和转换后的坐标。这样能快速定位是驱动问题还是Mapper问题。我曾经靠这个办法,半小时就找到了一个困扰团队三天的bug。
6.5 小结
SingleTouchMapper的process()函数,说白了就是三步走:更新状态、转换坐标、封装事件。虽然现在多点触摸已经普及了,但理解单点触摸的处理逻辑,能帮你更好地理解整个Input系统的工作机制。