Android多點觸摸的實現

 

第一章 摘要 

Linux內核支持的基礎上, Android 在其 2.0 源碼中加入多點觸摸功能。由此觸摸屏在 Android frameworks 被完全分爲 2 種實現途徑:單點觸摸屏的單點方式,多點觸摸屏的單點和多點方式。

 

第二章 軟件位

Linux input.h中,多點觸摸功能依賴於以下幾個主要的軟件位:

………………………..

#define SYN_REPORT0

#define SYN_CONFIG1

#define SYN_MT_REPORT2

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

#define ABS_MT_TOUCH_MAJOR0x30/* Major axis of touching ellipse */

#define ABS_MT_TOUCH_MINOR0x31/* Minor axis (omit if circular) */

#define ABS_MT_WIDTH_MAJOR0x32/* Major axis of approaching ellipse */

#define ABS_MT_WIDTH_MINOR0x33/* Minor axis (omit if circular) */

#define ABS_MT_ORIENTATION0x34/* Ellipse orientation */

#define ABS_MT_POSITION_X0x35/* Center X ellipse position */

#define ABS_MT_POSITION_Y0x36/* Center Y ellipse position */

#define ABS_MT_TOOL_TYPE0x37/* Type of touching device */

#define ABS_MT_BLOB_ID0x38/* 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 在軌跡球和單點屏事件方式中也全使用了新的變量名,以方便多點屏事件同樣能使用這些變量,所以修改時還需要注意許多細節方面。

 

 轉載自:“炸水生菜 ” 博客:http://yuesun.blog.51cto.com

發佈了0 篇原創文章 · 獲贊 2 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章