Android多点触摸实现

第一章 摘要

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

第二章 软件位

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

……………………… ..

  1. define SYN_REPORT 0
  1. define SYN_CONFIG 1
  1. define SYN_MT_REPORT 2

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

  1. define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */
  1. define ABS_MT_TOUCH_MINOR 0x31 /* Minor axis (omit if circular) */
  1. define ABS_MT_WIDTH_MAJOR 0x32 /* Major axis of approaching ellipse */
  1. define ABS_MT_WIDTH_MINOR 0x33 /* Minor axis (omit if circular) */
  1. define ABS_MT_ORIENTATION 0x34 /* Ellipse orientation */
  1. define ABS_MT_POSITION_X 0x35 /* Center X ellipse position */
  1. define ABS_MT_POSITION_Y 0x36 /* Center Y ellipse position */
  1. define ABS_MT_TOOL_TYPE 0x37 /* Type of touching device */
  1. define ABS_MT_BLOB_ID 0x38 /* Group a set of packets as a blob */

…………………………

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

………………… ..

public class RawInputEvent {

……………… .

  public static final int CLASS_TOUCHSCREEN_MT = 0x00000010;  

……………… ..

  public static final int ABS_MT_TOUCH_MAJOR = 0x30;  
 public static final int ABS_MT_TOUCH_MINOR = 0x31;  
 public static final int ABS_MT_WIDTH_MAJOR = 0x32;  
 public static final int ABS_MT_WIDTH_MINOR = 0x33;  
 public static final int ABS_MT_ORIENTATION = 0x34;  
 public static final int ABS_MT_POSITION_X = 0x35;  
 public static final int ABS_MT_POSITION_Y = 0x36;  
 public static final int ABS_MT_TOOL_TYPE = 0x37;  
 public static final int ABS_MT_BLOB_ID = 0x38;  

………………… .

public static final int SYN_REPORT = 0;

 public static final int SYN_CONFIG = 1;  

public static final int SYN_MT_REPORT = 2;

……………… ..

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

int EventHub::open_device(const char *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("It is a multi-touch screen!");

}

//single-touch?

else if (test_bit(BTN_TOUCH, key_bitmask)

&& test_bit(ABS_X, abs_bitmask)

&& test_bit(ABS_Y, abs_bitmask)) {

device->classes |= CLASS_TOUCHSCREEN;

// LOGI("It is a single-touch screen!");

}

……………… ..

}

我们知道,在触摸屏驱动中,通常在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(struct input_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 事件放入一个队列:

public abstract class KeyInputQueue {

……………………

Thread mThread = new Thread("InputDeviceReader") {

       public void run() {  
           android.os.Process.setThreadPriority(  
                   android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY);  


           try {  
               RawInputEvent ev = new RawInputEvent();  
               while (true) {  
                     InputDevice di;  
                      // block, doesn't release the monitor  
                      readEvent(ev);  

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

                       synchronized (mFirst) {  
                           di = newInputDevice(ev.deviceId);  
                           mDevices.put(ev.deviceId, di);  
                           configChanged = true;  
                       }  
                   } else if (ev.type == RawInputEvent.EV_DEVICE_REMOVED) {  
                       synchronized (mFirst) {  
                           Log.i(TAG, "Device removed: id=0x"  
                                   + Integer.toHexString(ev.deviceId));  
                           di = mDevices.get(ev.deviceId);  
                           if (di != null) {  
                               mDevices.delete(ev.deviceId);  
                               configChanged = true;  
                           } else {  
                               Log.w(TAG, "Bad device id: " + ev.deviceId);  
                           }  
                       }  
                   } else {  
                       di = getInputDevice(ev.deviceId);  


                       // first crack at it  
                       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)) {  
                     /* 键盘按键事件  */  
                      …………………… .  
                     } else if (ev.type == RawInputEvent.EV_KEY) {  
                     /* 下面是 EV_KEY 事件分支,只支持单点的触摸屏有按键事件,  
                      * 而支持多点的触摸屏没有按键事件,只有绝对坐标事件  
  • /
                          if (ev.scancode == RawInputEvent.BTN_TOUCH &&  
                                   (classes&(RawInputEvent.CLASS_TOUCHSCREEN  
                                           |RawInputEvent.CLASS_TOUCHSCREEN_MT))  
                                           == RawInputEvent.CLASS_TOUCHSCREEN) {  
                     /* 只支持单点的触摸屏的按键事件  */  
                       …………………………………  
                            } else if (ev.scancode == RawInputEvent.BTN_MOUSE &&  
                                   (classes&RawInputEvent.CLASS_TRACKBALL) != 0) {  
                     /* 鼠标和轨迹球  */  
                       ……………………… .  
                      } else if (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;  
                           } else if (ev.scancode == RawInputEvent.ABS_MT_POSITION_X) {  
                               di.mAbs.changed = true;  
                               di.mAbs.mNextData[di.mAbs.mAddingPointerOffset  
                                   + MotionEvent.SAMPLE_X] = ev.value;                            
                           } else if (ev.scancode == RawInputEvent.ABS_MT_POSITION_Y) {  
                               di.mAbs.changed = true;  
                               di.mAbs.mNextData[di.mAbs.mAddingPointerOffset  
                                   + MotionEvent.SAMPLE_Y] = ev.value;                              
                           } else if (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 确定。  
            */  
                     } else if (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;  
                           } else if (ev.scancode == RawInputEvent.ABS_Y) {  
                               di.mAbs.changed = true;  
                               di.curTouchVals[MotionEvent.SAMPLE_Y] = ev.value;  
                           } else if (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;  
                           } else if (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 的值,从而将  
                    * 新增的点的参数保存到下一组偏移量的位置。  
                    */  
                               …………………… .  
                             final int newOffset = (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;  
                     }  
                       ……………… .  
                   } else if (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:

private InputDevice newInputDevice(int deviceId) {

    int classes = getDeviceClasses(deviceId);  

String name = getDeviceName(deviceId);

InputDevice.AbsoluteInfo absX;

   InputDevice.AbsoluteInfo absY;  
   InputDevice.AbsoluteInfo absPressure;  
   InputDevice.AbsoluteInfo absSize;  
   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");  
    } else if ((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;  
    }          
       return new InputDevice(deviceId, classes, name, absX, absY, absPressure, absSize);  
}  



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

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

InputDeviceClass.MotionStateClass.generateAbsMotion()。

public class InputDevice {

 ……………………………  

static class MotionState { //下面是这个内部类的几个函数

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

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

int mLastNumPointers = 0;

final int[] mLastData = new int[MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS];

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

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

int mNextNumPointers = 0;

final int[] mNextData = new int[(MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS)

+ MotionEvent.NUM_SAMPLE_DATA];

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

    int[] generateAveragedData(int upOrDownPointer, int lastNumPointers,  
               int nextNumPointers) {  //平滑处理  
    …………………………………… .  
   }  
    private boolean assignPointer(int nextIndex, boolean allowOverlap) { //指派按键  
    ……………………………………  
   }  
    private int updatePointerIdentifiers() { //更新按键 ID  
    ………………………………… .  
   }  
    void removeOldPointer(int index) {  
    ……………………………………  
   }  
    MotionEvent generateAbsMotion(InputDevice device, long curTime,  
               long curTimeNano, Display display, int orientation,  
               int metaState) {  
    ……………………………………  

int upOrDownPointer = updatePointerIdentifiers();

    final int numPointers = 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 对这些点进行二次处理 */  
    ……………………………………  
   }  

MotionEvent generateRelMotion(InputDevice device, long curTime,

               long curTimeNano, int orientation, int metaState) {  

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

   ………………………………………… ..  
  }  
   void finish() {       //结束这轮动作  
           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 FrameWork――Touch事件派发过程详解
  2. 自定义View系列教程06--详解View的Touch事件处理
  3. Android ListView内部组件事件响应
  4. EditText单击触发onclick事件处理
  5. Android Touch事件原理加实例分析
  6. Android 双指同时点击事件模拟
  7. Android 应用初始化及窗体事件的分发
  8. Android Studio中Edittext监听回车事件,捕获手机和扫描枪的Enter

随机推荐

  1. 我们可以控制你看到的内容:主流IPTV远程代
  2. 如何仿照OSINT模式进行机密信息的收集与
  3. 智能电视再曝漏洞——Supra智能云电视漏
  4. Yubikey的武器化之路,以***kiosk自助设备
  5. Windows 10安全指南
  6. 17年未修复的Firefox本地文件窃取漏洞分
  7. Buhtrap在最新监控活动中使用多个0 day漏
  8. js脚本基础
  9. 通过wireshark抓包来讲解HTTP中Connectio
  10. 模拟“嫦娥五号”探月小游戏【附源码】