转载:本文出自 “炸水生菜 ” 博客:http://yuesun.blog.51cto.com


第一章 摘要

Linux 内核支持的基础上, Android 在其 2.0 源码中加入多点触摸功能。由此触摸屏在 Android frameworks 被完全分为 2 种实现途径:单点触摸屏的单点方式,多点触摸屏的单点和多点方式。

第二章 软件位

Linux input.h 中,多点触摸功能依赖于以下几个主要的软件位:

……………………… ..

#defineSYN_REPORT 0

#defineSYN_CONFIG 1

#defineSYN_MT_REPORT 2

……………………… ...

#defineABS_MT_TOUCH_MAJOR 0x30 /*Majoraxisoftouchingellipse*/

#defineABS_MT_TOUCH_MINOR 0x31 /*Minoraxis(omitifcircular)*/

#defineABS_MT_WIDTH_MAJOR 0x32 /*Majoraxisofapproachingellipse*/

#defineABS_MT_WIDTH_MINOR 0x33 /*Minoraxis(omitifcircular)*/

#defineABS_MT_ORIENTATION 0x34 /*Ellipseorientation*/

#defineABS_MT_POSITION_X 0x35 /*CenterXellipseposition*/

#defineABS_MT_POSITION_Y 0x36 /*CenterYellipseposition*/

#defineABS_MT_TOOL_TYPE 0x37 /*Typeoftouchingdevice*/

#defineABS_MT_BLOB_ID 0x38 /*Groupasetofpacketsasablob*/

…………………………

Android 中对应的软件位定义在 RawInputEvent.java :

………………… ..

publicclassRawInputEvent{

……………… .

publicstaticfinalintCLASS_TOUCHSCREEN_MT=0x00000010;

……………… ..

publicstaticfinalintABS_MT_TOUCH_MAJOR=0x30;

publicstaticfinalintABS_MT_TOUCH_MINOR=0x31;

publicstaticfinalintABS_MT_WIDTH_MAJOR=0x32;

publicstaticfinalintABS_MT_WIDTH_MINOR=0x33;

publicstaticfinalintABS_MT_ORIENTATION=0x34;

publicstaticfinalintABS_MT_POSITION_X=0x35;

publicstaticfinalintABS_MT_POSITION_Y=0x36;

publicstaticfinalintABS_MT_TOOL_TYPE=0x37;

publicstaticfinalintABS_MT_BLOB_ID=0x38;

………………… .

publicstaticfinalintSYN_REPORT=0;

publicstaticfinalintSYN_CONFIG=1;

publicstaticfinalintSYN_MT_REPORT=2;

……………… ..

Android 中,多点触摸的实现方法在具体的代码实现中和单点是完全区分开的。在 Android 代码的 EventHub.cpp 中,单点屏和多点屏由如下代码段来判定:

intEventHub::open_device(constchar*deviceName)

{

………………………

if(test_bit(ABS_MT_TOUCH_MAJOR,abs_bitmask)

&&test_bit(ABS_MT_POSITION_X,abs_bitmask)

&&test_bit(ABS_MT_POSITION_Y,abs_bitmask)){

device->classes|=CLASS_TOUCHSCREEN|CLASS_TOUCHSCREEN_MT;

// LOGI("Itisamulti-touchscreen!");

}

//single-touch?

elseif(test_bit(BTN_TOUCH,key_bitmask)

&&test_bit(ABS_X,abs_bitmask)

&&test_bit(ABS_Y,abs_bitmask)){

device->classes|=CLASS_TOUCHSCREEN;

// LOGI("Itisasingle-touchscreen!");

}

……………… ..

}

我们知道,在触摸屏驱动中,通常在probe 函数中会调用 input_set_abs_params 给设备的input_dev 结构体初始化,这些 input_dev 的参数会在 Android EventHub.cpp 中被读取。如上可知,如果我们的触摸屏想被当成多点屏被处理,只需要在驱动中给 input_dev 额外增加以下几个参数即可:

input_set_abs_params(mcs_data.input,ABS_MT_POSITION_X, pdata->abs_x_min,pdata->abs_x_max, 0,0);

input_set_abs_params(mcs_data.input,ABS_MT_POSITION_Y, pdata->abs_y_min,pdata->abs_y_max, 0,0);

input_set_abs_params(mcs_data.input,ABS_MT_TOUCH_MAJOR,0,15, 0,0);

//相当于单点屏的 ABX_PRESSURE

input_set_abs_params(mcs_data.input,ABS_MT_WIDTH_MAJOR,0,15, 0,0);

//相当于单点屏的 ABS_TOOL_WIDTH

注:

为了让我们的驱动代码支持所有的Android 版本,无论是多点屏还是单点屏,一般都会保留单点屏的事件,如 ABS_TOUCH,ABS_PRESSURE,ABS_X,ABS_Y 等。另外,由于在 Android2.0 前支持多点的 frameworks 大多是用 HAT0X,HAT0Y 来实现的,所以一般也会上报这 2 个事件。

第三章 同步方式

由于多点触摸技术需要采集到多个点,然后再一起处理这些点,所以在软件实现中需要保证每一波点的准确性和完整性。因此,Linux 内核提供了 input_mt_sync(structinput_dev*input) 函数。在每波的每个点上报后需要紧跟一句 input_mt_sync(), 当这波所有点上报后再使用 input_sync() 进行同步。例如一波要上报 3 个点:

/*上报点 1*/

…………… ..

input_mt_sync(input);

/*上报点 2*/

…………… ..

input_mt_sync(input);

/*上报点 3*/

…………… ..

input_mt_sync(input);

input_sync(input);

注:即使是仅上报一个点的单点事件,也需要一次input_my_sync


Android KeyInputQueue.java 中,系统创建了一个线程,然后把所有的 Input 事件放入一个队列:

publicabstractclassKeyInputQueue{

……………………

ThreadmThread=newThread("InputDeviceReader"){

publicvoidrun(){

android.os.Process.setThreadPriority(

android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY);

try{

RawInputEventev=newRawInputEvent();

while(true){

InputDevicedi;

//block,doesn'treleasethemonitor

readEvent(ev);

if(ev.type==RawInputEvent.EV_DEVICE_ADDED){

synchronized(mFirst){

di=newInputDevice(ev.deviceId);

mDevices.put(ev.deviceId,di);

configChanged=true;

}

}elseif(ev.type==RawInputEvent.EV_DEVICE_REMOVED){

synchronized(mFirst){

Log.i(TAG,"Deviceremoved:id=0x"

+Integer.toHexString(ev.deviceId));

di=mDevices.get(ev.deviceId);

if(di!=null){

mDevices.delete(ev.deviceId);

configChanged=true;

}else{

Log.w(TAG,"Baddeviceid:"+ev.deviceId);

}

}

}else{

di=getInputDevice(ev.deviceId);

//firstcrackatit

send=preprocessEvent(di,ev);

if(ev.type==RawInputEvent.EV_KEY){

di.mMetaKeysState=makeMetaState(ev.keycode,

ev.value!=0,di.mMetaKeysState);

mHaveGlobalMetaState=false;

}

}

if(di==null){

continue;

}

if(configChanged){

synchronized(mFirst){

addLocked(di,SystemClock.uptimeMillis(),0,

RawInputEvent.CLASS_CONFIGURATION_CHANGED,

null);

}

}

if(!send){

continue;

}

synchronized(mFirst){

……………………… .

if(type==RawInputEvent.EV_KEY&&

(classes&RawInputEvent.CLASS_KEYBOARD)!=0&&

(scancode<RawInputEvent.BTN_FIRST||

scancode>RawInputEvent.BTN_LAST)){

/*键盘按键事件 */

…………………… .

}elseif(ev.type==RawInputEvent.EV_KEY){

/*下面是 EV_KEY 事件分支,只支持单点的触摸屏有按键事件,

*而支持多点的触摸屏没有按键事件,只有绝对坐标事件

*/

if(ev.scancode==RawInputEvent.BTN_TOUCH&&

(classes&(RawInputEvent.CLASS_TOUCHSCREEN

|RawInputEvent.CLASS_TOUCHSCREEN_MT))

==RawInputEvent.CLASS_TOUCHSCREEN){

/*只支持单点的触摸屏的按键事件 */

…………………………………

}elseif(ev.scancode==RawInputEvent.BTN_MOUSE&&

(classes&RawInputEvent.CLASS_TRACKBALL)!=0){

/*鼠标和轨迹球 */

……………………… .

}elseif(ev.type==RawInputEvent.EV_ABS&&

(classes&RawInputEvent.CLASS_TOUCHSCREEN_MT)!=0){

/*下面才是多点触摸屏上报的事件 */

if(ev.scancode==RawInputEvent.ABS_MT_TOUCH_MAJOR){

di.mAbs.changed=true;

di.mAbs.mNextData[di.mAbs.mAddingPointerOffset

+MotionEvent.SAMPLE_PRESSURE]=ev.value;

}elseif(ev.scancode==RawInputEvent.ABS_MT_POSITION_X){

di.mAbs.changed=true;

di.mAbs.mNextData[di.mAbs.mAddingPointerOffset

+MotionEvent.SAMPLE_X]=ev.value;

}elseif(ev.scancode==RawInputEvent.ABS_MT_POSITION_Y){

di.mAbs.changed=true;

di.mAbs.mNextData[di.mAbs.mAddingPointerOffset

+MotionEvent.SAMPLE_Y]=ev.value;

}elseif(ev.scancode==RawInputEvent.ABS_MT_WIDTH_MAJOR){

di.mAbs.changed=true;

di.mAbs.mNextData[di.mAbs.mAddingPointerOffset

+MotionEvent.SAMPLE_SIZE]=ev.value;

}

/*上面这段就是多点触摸屏要用到的事件上报部分 ;

*使用一个数组 mNextData 来保存,其中 di.mAbs.mAddingPointerOffset

*是当前点的偏移量,在每个点中还在 MotionEvent 中定义了 X,Y,PRESSURE

*SIZE等偏移量,多点触摸屏的压力值由绝对坐标事件 ABS_MT_TOUCH_MAJOR 确定。

*/

}elseif(ev.type==RawInputEvent.EV_ABS&&

(classes&RawInputEvent.CLASS_TOUCHSCREEN)!=0){

/*这里是对单点触摸屏上报坐标事件的新的处理方法,同样使用了数组来保存 */

if(ev.scancode==RawInputEvent.ABS_X){

di.mAbs.changed=true;

di.curTouchVals[MotionEvent.SAMPLE_X]=ev.value;

}elseif(ev.scancode==RawInputEvent.ABS_Y){

di.mAbs.changed=true;

di.curTouchVals[MotionEvent.SAMPLE_Y]=ev.value;

}elseif(ev.scancode==RawInputEvent.ABS_PRESSURE){

di.mAbs.changed=true;

di.curTouchVals[MotionEvent.SAMPLE_PRESSURE]=ev.value;

di.curTouchVals[MotionEvent.NUM_SAMPLE_DATA

+MotionEvent.SAMPLE_PRESSURE]=ev.value;

}elseif(ev.scancode==RawInputEvent.ABS_TOOL_WIDTH){

di.mAbs.changed=true;

di.curTouchVals[MotionEvent.SAMPLE_SIZE]=ev.value;

di.curTouchVals[MotionEvent.NUM_SAMPLE_DATA

+MotionEvent.SAMPLE_SIZE]=ev.value;

}

…………………………………………… .}

/*下面是关键的同步处理方法 */

if(ev.type==RawInputEvent.EV_SYN

&&ev.scancode==RawInputEvent.SYN_MT_REPORT

&&di.mAbs!=null){

/*在这里实现了对 SYN_MT_REPORT 事件的处理,

*改变了 di.mAbs.mAddingPointerOffset 的值,从而将

*新增的点的参数保存到下一组偏移量的位置。

*/

…………………… .

finalintnewOffset=(num<=InputDevice.MAX_POINTERS)

?(num*MotionEvent.NUM_SAMPLE_DATA)

:(InputDevice.MAX_POINTERS*

MotionEvent.NUM_SAMPLE_DATA);

di.mAbs.mAddingPointerOffset=newOffset;

di.mAbs.mNextData[newOffset

+MotionEvent.SAMPLE_PRESSURE]=0;

}

……………… .

}elseif(send||(ev.type==RawInputEvent.EV_SYN

&&ev.scancode==RawInputEvent.SYN_REPORT)){

/*这里实现了对 SYN_REPORT 事件的处理

*如果是单点触摸屏,即使用 di.curTouchVals 数组保存的点

*转化为多点触摸屏的 mNextData 数组保存

*最后是调用 InputDevice 中的 generateAbsMotion 处理这个数组。这个函数

*的具体实现方法将在后面补充

*/

………………………… ..

ms.finish(); //重置所有点和偏移量

…………………… ..

}

由于上层的代码仍然使用ABS_X,ABS_Y 这些事件,为了使多点触摸屏代码有良好的兼容性,在 KeyInputQueue.java 的最后,我们将多点事件类型转化为单点事件类型,返回一个新的 InputDevice:

privateInputDevicenewInputDevice(intdeviceId){

intclasses=getDeviceClasses(deviceId);

Stringname=getDeviceName(deviceId);

InputDevice.AbsoluteInfoabsX;

InputDevice.AbsoluteInfoabsY;

InputDevice.AbsoluteInfoabsPressure;

InputDevice.AbsoluteInfoabsSize;

if((classes&RawInputEvent.CLASS_TOUCHSCREEN_MT)!=0){

absX=loadAbsoluteInfo(deviceId,

RawInputEvent.ABS_MT_POSITION_X,"X");

absY=loadAbsoluteInfo(deviceId,

RawInputEvent.ABS_MT_POSITION_Y,"Y");

absPressure=loadAbsoluteInfo(deviceId,

RawInputEvent.ABS_MT_TOUCH_MAJOR,"Pressure");

absSize=loadAbsoluteInfo(deviceId,

RawInputEvent.ABS_MT_WIDTH_MAJOR,"Size");

}elseif((classes&RawInputEvent.CLASS_TOUCHSCREEN)!=0){

absX=loadAbsoluteInfo(deviceId,

RawInputEvent.ABS_X,"X");

absY=loadAbsoluteInfo(deviceId,

RawInputEvent.ABS_Y,"Y");

absPressure=loadAbsoluteInfo(deviceId,

RawInputEvent.ABS_PRESSURE,"Pressure");

absSize=loadAbsoluteInfo(deviceId,

RawInputEvent.ABS_TOOL_WIDTH,"Size");

}else{

absX=null;

absY=null;

absPressure=null;

absSize=null;

}

returnnewInputDevice(deviceId,classes,name,absX,absY,absPressure,absSize);

}


第四章 触摸事件 数组的处理

上面我们曾说到 generateAbsMotion 这个方法,它们在InputDevice 类的内部类 MotionState 中实现,该类被定义为 InputDevice 类的静态成员类 (staticclass) ,调用它们可以直接使用:

InputDeviceClass.MotionStateClass.generateAbsMotion()

publicclassInputDevice{

……………………………

staticclassMotionState{ //下面是这个内部类的几个函数

……………………………… .

/*mLastNumPointers为上一个动作在触屏上按键的个数 */

intmLastNumPointers=0;

finalint[]mLastData=newint[MotionEvent.NUM_SAMPLE_DATA*MAX_POINTERS];

/*mNextNumPointers为下一个动作在触屏上按键的个数 */

/*通过对这 2 个值大小的判断,可以确认新的动作方式 */

intmNextNumPointers=0;

finalint[]mNextData=newint[(MotionEvent.NUM_SAMPLE_DATA*MAX_POINTERS)

+MotionEvent.NUM_SAMPLE_DATA];

………………………………… .

int[]generateAveragedData(intupOrDownPointer,intlastNumPointers,

intnextNumPointers){ //平滑处理

…………………………………… .

}

privatebooleanassignPointer(intnextIndex,booleanallowOverlap){ //指派按键

……………………………………

}

privateintupdatePointerIdentifiers(){ //更新按键 ID

………………………………… .

}

voidremoveOldPointer(intindex){

……………………………………

}

MotionEventgenerateAbsMotion(InputDevicedevice,longcurTime,

longcurTimeNano,Displaydisplay,intorientation,

intmetaState){

……………………………………

intupOrDownPointer=updatePointerIdentifiers();

finalintnumPointers=mLastNumPointers;

………………………………………

/*对行为的判断 */

if(nextNumPointers!=lastNumPointers){ //前后在触屏上点个数不同,说明有手指 up down

if(nextNumPointers>lastNumPointers){

if(lastNumPointers==0){ //上次触屏上没有按键,新值又大,说明有按键按下

action=MotionEvent.ACTION_DOWN;

mDownTime=curTime;

}else{ //有新点按下,分配给新点 ID

action=MotionEvent.ACTION_POINTER_DOWN

|(upOrDownPointer<<MotionEvent.ACTION_POINTER_ID_SHIFT);

}

}else{ //新动作比原来 pointer 数量少

if(numPointers==1){ //原来只有 1 个点按下,所以现在的动作是全部按键 up

action=MotionEvent.ACTION_UP;

}else{ //原来有多点按下,现在是 ACTION_POINTER_UP 动作,

action=MotionEvent.ACTION_POINTER_UP

|(upOrDownPointer<<MotionEvent.ACTION_POINTER_ID_SHIFT);

}

}

currentMove=null;

}else{ //前后触屏 pointer 个数相同,所以是移动动作 ACTION_MOVE

action=MotionEvent.ACTION_MOVE;

}

/*后面则是根据屏幕的 height width 以及屏幕方向 orientation 对这些点进行二次处理*/

……………………………………

}

MotionEventgenerateRelMotion(InputDevicedevice,longcurTime,

longcurTimeNano,intorientation,intmetaState){

/*轨迹球等的处理方式 */

………………………………………… ..

}

voidfinish(){ //结束这轮动作

mNextNumPointers=mAddingPointerOffset=0;

mNextData[MotionEvent.SAMPLE_PRESSURE]=0;

}

…………………………………… .

}

……………………………… .

……………………………………

}

第五章 接口

我们平时所看到的用2 个手指对图片放大缩小、旋转等手势都是由应用程序编写浏览器实现的。这些应用程序大多会使用 Android2.0 以上的在 MotionEvent.java 中实现的新的接口。所以,我们还需要给 MotionEvent 类补充尽量全的接口。这里可以完全参照 google 新的 android 代码。

第六章 总结

综上,在硬件支持基础上,Android1.6 如果要实现多点触摸功能,主要工作可简述为以下几个方面:

1、 驱动中,除了增加多点的事件上报方式,还要完全更改单点的事件上报方式。

2、 Android Frameworks 层需要修改的文件有: EventHub.cpp RawInputEvent.java KeyInputQueue.java InputDevice.java MotionEvent.java

3、 编写新的支持多点触摸功能的多媒体浏览器。

4、 为了代码简练,android2.0 在轨迹球和单点屏事件方式中也全使用了新的变量名,以方便多点屏事件同样能使用这些变量,所以修改时还需要注意许多细节方面。

更多相关文章

  1. android画经过多点的曲线
  2. Android ListView:实现item内部控件的点击事件
  3. Android触摸事件分发
  4. Android的MotionEvent事件分发机制
  5. Android Touch 触摸事件
  6. 源码解析Android中的事件处理
  7. 【Android每周专题】触摸屏事件
  8. Android事件分发之dispatchTouchEvent()

随机推荐

  1. XML学习(二)详解DOM操作XML文档
  2. XML实战秘籍第三卷:动态分页
  3. XML学习(一)元素,属性,读取详解
  4. XML实战秘籍第二卷:动态查询
  5. 详细介绍XML和HTML常用转义字符
  6. XML实战秘籍第一卷:动态排序
  7. 详细介绍xml的使用方法总结
  8. XML基础讲解之结构与语法
  9. 详细介绍Android 解析XML文件和生成XML文
  10. XML文件数减少的示例代码分享