安卓自定義View進階《十六》——多點觸控詳解

Android 多點觸控詳解荒叼,在前面的幾篇文章中我們大致了解了 Android 中的事件處理流程和一些簡單的處理方案沿猜,本次帶大家了解 Android 多點觸控相關(guān)的一些知識敌蚜。

多點觸控 ( Multitouch接谨,也稱 Multi-touch )蛇尚,即同時接受屏幕上多個點的人機交互操作芽唇,多點觸控是從 Android 2.0 開始引入的功能,在 Android 2.2 時對這一部分進行了重新設(shè)計佣蓉。

在本文開始之前披摄,先回顧一下 MotionEvent詳解 中提到過的內(nèi)容:

  • Android 將所有的事件都封裝進了 Motionvent 中。
  • 我們可以通過復(fù)寫 onTouchEvent 或者設(shè)置 OnTouchListener 來獲取 View 的事件勇凭。
  • 多點觸控獲取事件類型請使用 getActionMasked() 。
  • 追蹤事件流請使用 PointId义辕。

多點觸控相關(guān)的事件:

多點觸控相關(guān)的方法:

一虾标、多點觸控相關(guān)問題

在引入多點觸控之前,事件的類型很少灌砖,基本事件類型只有按下(down)璧函、移動(move) 和 抬起(up),即便加上那些特殊的事件類型也只有幾種而已基显,所以我們可以用幾個常量來標記這些事件蘸吓,在使用的時候使用 getAction() 方法來獲取具體的事件,之后和這些常量進行對比就行了撩幽。
在 Android 2.0 版本的時候库继,開始引入多點觸控技術(shù),由于技術(shù)上并不成熟窜醉,硬件和驅(qū)動也跟不上宪萄,多數(shù)設(shè)備只能支持追蹤兩三個點而已,因此在設(shè)計 API 上采取了一種簡單粗暴的方案榨惰,添加了幾個常量用于多點觸控的事件類型的判斷拜英。



這些事件類型是用來判斷非主要手指(第一個按下的稱為主要手指)的按下和抬起,使用起來大概是這樣子:

switch (event.getAction()) {
    case MotionEvent.ACTION_DOWN:           break;
    case MotionEvent.ACTION_UP:             break;
    case MotionEvent.ACTION_MOVE:           break;
    case MotionEvent.ACTION_POINTER_1_DOWN: break;
    case MotionEvent.ACTION_POINTER_2_DOWN: break;
    case MotionEvent.ACTION_POINTER_3_DOWN: break;
    case MotionEvent.ACTION_POINTER_1_UP:   break;
    case MotionEvent.ACTION_POINTER_2_UP:   break;
    case MotionEvent.ACTION_POINTER_3_UP:   break;
}

看到這里可能會產(chǎn)生以下的一些疑問琅催?

1.為什么沒有 ACTION_POINTER_X_MOVE ?

在多指觸控中所有的移動事件都是使用 ACTION_MOVE居凶, 并沒有追蹤某一個手指的 move 事件類型虫给,個人猜測主要是因為:很難無歧義的實現(xiàn)單獨追蹤每一個手指。

要理解這個侠碧,首先要明白設(shè)備是如何識別多點觸控的狰右,設(shè)備沒有眼睛,不能像我們?nèi)艘粯涌吹接袔讉€手指(或者觸控筆)在屏幕上舆床。
目前大多數(shù) Android 設(shè)備都是電容屏棋蚌,它們感知觸摸是利用手指(觸控筆)與屏幕接觸產(chǎn)生的微小電流變化,之后通過計算這些電流變化來得出具體的觸摸位置挨队,在多點觸控中谷暮,當兩個觸摸點足夠靠近時,設(shè)備實際上是無法分清這兩個點的盛垦。因此當兩個觸摸點靠近(重合)后再分開湿弦,設(shè)備很可能就無法正確的追蹤兩個點了,所以也很難實現(xiàn)無歧義的追蹤每一個點腾夯。

并且從軟件上來說颊埃,事件的編號產(chǎn)生和復(fù)用也是一個大問題,例如下面的場景:



注意觀察上面編號和id的變化蝶俱,有兩個問題班利,1、B手指的編號變化了榨呆。2罗标、A手指和C手指id是相同的(A手指抬起后,C手指按下替代了A手指)积蜻。所以這就引出了一個問題:如果存在 ACTION_POINTER_X_MOVE闯割,那么X應(yīng)該用什么標志呢?編號會變化竿拆,id雖然不會變化宙拉,但id會被復(fù)用,例如A手指抬起后C手指按下丙笋,C手指復(fù)用了A手指的id谢澈。所以不論使用哪一個都不能保證唯一性。

當然了不见,解決問題最好的方式就是把問題拋出去澳化,既然從硬件和軟件上都不能保證唯一性和不變性,就不做區(qū)分了稳吮,因此所有的 move 事件都是 ACTION_MOVE, 具體是哪個手指產(chǎn)生的 move 用戶可以結(jié)合其他事件(按下和抬起)來綜合判斷缎谷。

2.超過4個手指怎么辦?

2.0 兼容版,在2.2 之前的設(shè)計中列林,其提供的常量最多能判斷四個手指的抬起和落下瑞你,當超過四個手指時怎么辦呢?
由于在 2.2 版本之前希痴,由于沒有 getActionMasked 方法者甲,我們可以自己自己手動進行計算,例如下面這樣 :

String TAG = "Gcs";

int action = event.getAction() & MotionEvent.ACTION_MASK;
int index = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
        >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;

switch (action) {
    case MotionEvent.ACTION_DOWN:
        Log.e(TAG,"第1個手指按下");
        break;
    case MotionEvent.ACTION_UP:
        Log.e(TAG,"最后1個手指抬起");
        break;
    case MotionEvent.ACTION_POINTER_1_DOWN: // 此時相當于 ACTION_POINTER_DOWN
        Log.e(TAG,"第"+(index+1)+"個手指按下");
        break;
    case MotionEvent.ACTION_POINTER_1_UP:   // 此時相當于 ACTION_POINTER_UP
        Log.e(TAG,"第"+(index+1)+"個手指抬起");
        break;
}

在上面的例子中有幾點比較關(guān)鍵

2.1砌创、action 與 Index 的獲得

我們在 MotionEvent詳解 中了解過虏缸,Android中的事件一般用最后8位來表示事件類型,再往前8位來表示Index嫩实。
例如多指觸控的按下事件刽辙,其事件類型是 0x00000005, 其Index標志位是 0x00000005甲献,隨著更多的手指按下宰缤,其中變化的部分是 Index 標志位,最后兩位是始終不變的晃洒,所以我們只要能將這兩個分離開就行了慨灭。
取得事件類型(action)
// 獲取事件類型

int action = event.getAction() & MotionEvent.ACTION_MASK;

這個非常簡單,ACTION_MASK=0x000000ff球及, 與 getAction() 進行按位與操作后保留最后8位內(nèi)容(十六進制每一個字符轉(zhuǎn)化為二進制是4位)氧骤。
例如:
0x00000105 & 0x000000ff = 0x00000005
取得事件索引(index)

// 獲取index編號
int index = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
        >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;

ACTION_POINTER_INDEX_MASK = 0x0000ff00
ACTION_POINTER_INDEX_SHIFT = 8

首先讓 getAction() 與 ACTION_POINTER_INDEX_MASK 按位與之后,只保留 Index 那8位桶略,之后再右移8位语淘,最終就拿到了 Index 的真實數(shù)值。

例如:
0x00000105 & 0x0000ff00 = 0x00000100
0x00000100 ? 8 = 0x00000001

**2.2际歼、用 ACTION_POINTER_1_DOWN 代替 **ACTION_POINTER_DOWN
這是因為在 2.0 版本的時候還沒有 ACTION_POINTER_DOWN 的這個常量,但是它們兩個點數(shù)值是相同的姑蓝,都是 0x00000005鹅心,這個你可以查看官方文檔或者源碼,甚至你直接寫 case 0x00000005 也行纺荧,抬起也是同理旭愧。
2.3、只考慮兼容 2.2 以上的版本
當然了宙暇,如果你不需要兼容 2.0 版本输枯,只需要兼容到 2.2 以上的話就很簡單了,像下面這樣:

String TAG = "Gcs";

int index = event.getActionIndex();

switch (event.getActionMasked()) {
    case MotionEvent.ACTION_DOWN:
        Log.e(TAG,"第1個手指按下");
        break;
    case MotionEvent.ACTION_UP:
        Log.e(TAG,"最后1個手指抬起");
        break;
    case MotionEvent.ACTION_POINTER_DOWN:
        Log.e(TAG,"第"+(index+1)+"個手指按下");
        break;
    case MotionEvent.ACTION_POINTER_UP:
        Log.e(TAG,"第"+(index+1)+"個手指抬起");
        break;
}

3. index 和 pointId 的變化規(guī)則
在 2.2 版本以上占贫,我們可以通過 getActionIndex() 輕松獲取到事件的索引(Index)桃熄,但是這個事件索引的變化還是有點意思的,Index 變化有以下幾個特點:

  1. 從 0 開始型奥,自動增長瞳收。
  2. 如果之前落下的手指抬起碉京,后面手指的 Index 會隨之減小。
  3. Index 變化趨向于第一次落下的數(shù)值(落下手指時螟深,前面有空缺會優(yōu)先填補空缺)谐宙。
  4. 對 move 事件無效。

下面我們逐條解釋一下具體含義界弧。
3.1凡蜻、從 0 開始,自動增長垢箕。
這一條非常簡單划栓,也很容易理解,而且在 MotionEvent詳解 中講解 getAction() 與 getActionMasked() 也簡單說過舰讹。

注意加粗的位置茅姜,數(shù)值隨著手指按下而不斷變大。
3.2月匣、如果之前落下的手指抬起钻洒,后面手指的 Index 會隨之減小。
這個也比較容易理解锄开,像下面這樣:


注意最后兩次觸發(fā)的事件素标,它的 Index 都是 1,這樣也比較容易解釋萍悴,當原本的第 2 個手指抬起后头遭,屏幕上就只剩下兩個手指了,之前的第 3 個手指就變成了第 2 個癣诱,于是抬起時觸發(fā)事件的 Index 為 1计维,即之前落下的手指抬起,后面手指的 Index 會隨之減小撕予。
3.3鲫惶、Index 變化趨向于第一次落下的數(shù)值(落下手指時,前面有空缺會優(yōu)先填補空缺)实抡。
這個就有點神奇了欠母,通過上一條規(guī)則,我們知道吆寨,某一個手指的 Index 可能會隨著其他手指的抬起而變小赏淌,這次我們用 4 個手指測試一下 Index 的變化趨勢。


這個要和上一個對比這看啄清,重點觀察第 3 個手指所觸發(fā)事件區(qū)別六水,在上一個示例中,隨著第 2 個手指的抬起,第 3 個手指變化為第 2(01) 個缩擂,所以抬起時觸發(fā)的是第 2 根手指的抬起事件(刪除線部分)鼠冕。

但是,如果第 2 個手指抬起后胯盯,落在屏幕上另外一個手指會怎樣懈费?經(jīng)過測試,發(fā)現(xiàn)另外落下的手指會替代之前第 2 個手指的位置博脑,系統(tǒng)判定為 2(01)憎乙,而不是順延下去變成 3(02),并且原本第3個手指的index變?yōu)樵瓉頂?shù)值(02)叉趣,但是如果繼續(xù)落下其他的手指泞边,數(shù)值則會順延。

即手指抬起時的 Index 會趨向于和按下時相同疗杉,雖然在手指數(shù)量不足時阵谚,Index 會變小,但是當手指變多時烟具,Index 會趨向于保持和按下時一樣梢什。

PS:由于程序是從0開始計數(shù)的,所以 0 就是 1朝聋, 1 就是 2 …

3.4嗡午、對 move 事件無效。
這個也比較容易理解冀痕,我們所取得的 Index 屬性實際上是從事件上分離下來的荔睹,但是 move 事件始終為 0x00000002,也就是說言蛇,在 move 時不論你移動哪個手指僻他,使用 getActionIndex() 獲取到的始終是數(shù)值 0。

既然 move 事件無法用事件索引(Index)區(qū)別腊尚,那么該如何區(qū)分 move 是那個手指發(fā)出的呢中姜?這就要用到 pointId 了,pointId 和 index 最大的區(qū)別就是 pointId 是不變的跟伏,始終為第一次落下時生成的數(shù)值,不會受到其他手指抬起和落下的影響翩瓜。

3.5受扳、pointId 與 index 的異同。

4. Move 相關(guān)事件

4.1 actionIndex 與 pointerIndex
在 move 中無法取得 actionIndex 的兔跌,我們需要使用 pointerIndex 來獲取更多的信息勘高,例如某個手指的坐標:

getX(int pointerIndex)
getY(int pointerIndex)

但是這個 pointerIndex 又是什么呢?和 actionIndex 有區(qū)別么?

實際上這個 pointerIndex 和 actionIndex 區(qū)別并不大华望,兩者的數(shù)值是相同的蕊蝗,你可以認為 pointerIndex 是特地為 move 事件準備的 actionIndex。

4.2 pointerIndex 與 pointerId

這兩個數(shù)值使用以下兩個方法相互轉(zhuǎn)換赖舟。

通常情況下蓬戚,pointerIndex 和 pointerId 是相同的,但也可能會因為某些手指的抬起而變得不同宾抓。

4.3 遍歷多點觸控
先來一個簡單的子漩,遍歷出多個手指的 move 事件:

String TAG = "Gcs";
switch (event.getActionMasked()) {
    case MotionEvent.ACTION_MOVE:
        for (int i = 0; i < event.getPointerCount(); i++) {
            Log.i("TAG", "pointerIndex="+i+", pointerId="+event.getPointerId(i));
            // TODO
        }
}

通過遍歷 pointerCount 獲取到所有的 pointerIndex,同時通過 pointerIndex 來獲取 pointerId石洗,可以通過不同手指抬起和按下后移動來觀察 pointerIndex 和 pointerId 的變化幢泼。
4.4 在多點觸控中追蹤單個手指
要實現(xiàn)追蹤單個手指還是有些麻煩的,需要同時使用上 actionIndex讲衫, pointerId 和 pointerIndex缕棵,例如,我們只追蹤第2個手指涉兽,并畫出其位置:

/**
 * 繪制出第二個手指第位置
 */
public class MultiTouchTest extends CustomView {
    String TAG = "Gcs";

    // 用于判斷第2個手指是否存在
    boolean haveSecondPoint = false;

    // 記錄第2個手指第位置
    PointF point = new PointF(0, 0);

    public MultiTouchTest(Context context) {
        this(context, null);
    }

    public MultiTouchTest(Context context, AttributeSet attrs) {
        super(context, attrs);

        mDeafultPaint.setAntiAlias(true);
        mDeafultPaint.setTextAlign(Paint.Align.CENTER);
        mDeafultPaint.setTextSize(30);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int index = event.getActionIndex();

        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_POINTER_DOWN:
                // 判斷是否是第2個手指按下
                if (event.getPointerId(index) == 1){
                    haveSecondPoint = true;
                    point.set(event.getY(), event.getX());
                }
                break;
            case MotionEvent.ACTION_POINTER_UP:
                // 判斷抬起的手指是否是第2個
                if (event.getPointerId(index) == 1){
                    haveSecondPoint = false;
                    point.set(0, 0);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (haveSecondPoint) {
                    // 通過 pointerId 來獲取 pointerIndex
                    int pointerIndex = event.findPointerIndex(1);
                    // 通過 pointerIndex 來取出對應(yīng)的坐標
                    point.set(event.getX(pointerIndex), event.getY(pointerIndex));
                }
                break;
        }

        invalidate();   // 刷新

        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.save();
        canvas.translate(mViewWidth/2, mViewHeight/2);
        canvas.drawText("追蹤第2個按下手指的位置", 0, 0, mDeafultPaint);
        canvas.restore();

        // 如果屏幕上有第2個手指則繪制出來其位置
        if (haveSecondPoint) {
            canvas.drawCircle(point.x, point.y, 50, mDeafultPaint);
        }
    }
}

這段代碼也非常短招驴,其核心就是通過判斷數(shù)值為 1 的 pointerId 是否存在,如果存在就在 move 的時候取出其坐標花椭,并繪制出來忽匈。


雖然邏輯簡單,但個人感覺寫起來還是有些麻煩矿辽,如果有更簡單的方案歡迎告訴我丹允。

二、如何使用多點觸控

多點觸控應(yīng)用還是比較廣泛的袋倔,至少目前大部分的圖片查看都需要用到多點觸控技術(shù)(用于拖動和縮放圖片)雕蔽。

但是在某些看似不需要多觸控的地方也需要對多點觸控進行判斷,只要是多點觸控可能引起錯誤的地方都應(yīng)該加上多點觸控的判斷宾娜。例如使用到 move 事件的時候批狐,由于 move 事件可能由多個手指同時觸發(fā),所以可能會出現(xiàn)同時被多個手指控制的情況前塔,如果不適當?shù)奶幚硐В@個 move 就可能由任何一個手指觸發(fā)。

舉一個簡單的例子:

如果我們需要一個可以用單指拖動的圖片华弓。假如我們不進行多指觸控的判斷食零,像下面這樣:

沒有針對多指觸控處理版本:

/**
 * 一個可以拖圖片動的 View
 */
public class DragView1 extends CustomView {
    String TAG = "Gcs";

    Bitmap mBitmap;         // 圖片
    RectF mBitmapRectF;     // 圖片所在區(qū)域
    Matrix mBitmapMatrix;   // 控制圖片的 matrix

    boolean canDrag = false;
    PointF lastPoint = new PointF(0, 0);

    public DragView1(Context context) {
        this(context, null);
    }

    public DragView1(Context context, AttributeSet attrs) {
        super(context, attrs);

        // 調(diào)整圖片大小
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.outWidth = 960/2;
        options.outHeight = 800/2;

        mBitmap = BitmapFactory.decodeResource(this.getResources(), R.drawable.drag_test, options);
        mBitmapRectF = new RectF(0,0,mBitmap.getWidth(), mBitmap.getHeight());
        mBitmapMatrix = new Matrix();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                // 判斷按下位置是否包含在圖片區(qū)域內(nèi)
                if (mBitmapRectF.contains((int)event.getX(), (int)event.getY())){
                    canDrag = true;
                    lastPoint.set(event.getX(), event.getY());
             }
                break;
            case MotionEvent.ACTION_UP:
                canDrag = false;
            case MotionEvent.ACTION_MOVE:
                if (canDrag) {
                    // 移動圖片
                    mBitmapMatrix.postTranslate(event.getX() - lastPoint.x, event.getY() - lastPoint.y);
                    // 更新上一次點位置
                    lastPoint.set(event.getX(), event.getY());

                    // 更新圖片區(qū)域
                    mBitmapRectF = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
                    mBitmapMatrix.mapRect(mBitmapRectF);

                    invalidate();
                }
                break;
        }

        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawBitmap(mBitmap, mBitmapMatrix, mDeafultPaint);
    }
}

這個版本非常簡單,當然了寂屏,如果正常使用(只使用一個手指)的話也不會出問題贰谣,但是當使用多個手指娜搂,且有抬起和按下的時候就可能出問題,下面用一個典型的場景演示一下:


注意在第二個手指按下吱抚,第一個手指抬起時百宇,此時原本的第二個手指會被識別為第一個,所以圖片會直接跳動到第二個手指位置秘豹。

為了不出現(xiàn)這種情況携御,我們可以判斷一下 pointId 并且只獲取第一個手指的數(shù)據(jù),這樣就能避免這種情況發(fā)生了憋肖,如下因痛。

針對多指觸控處理后版本:

/**
 * 一個可以拖圖片動的 View
 */
public class DragView extends CustomView {
    String TAG = "Gcs";

    Bitmap mBitmap;         // 圖片
    RectF mBitmapRectF;     // 圖片所在區(qū)域
    Matrix mBitmapMatrix;   // 控制圖片的 matrix

    boolean canDrag = false;
    PointF lastPoint = new PointF(0, 0);

    public DragView(Context context) {
        this(context, null);
    }

    public DragView(Context context, AttributeSet attrs) {
        super(context, attrs);

        BitmapFactory.Options options = new BitmapFactory.Options();
        options.outWidth = 960/2;
        options.outHeight = 800/2;

        mBitmap = BitmapFactory.decodeResource(this.getResources(), R.drawable.drag_test, options);
        mBitmapRectF = new RectF(0,0,mBitmap.getWidth(), mBitmap.getHeight());
        mBitmapMatrix = new Matrix();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_POINTER_DOWN:
                // ▼ 判斷是否是第一個手指 && 是否包含在圖片區(qū)域內(nèi)
                if (event.getPointerId(event.getActionIndex()) == 0 && mBitmapRectF.contains((int)event.getX(), (int)event.getY())){
                    canDrag = true;
                    lastPoint.set(event.getX(), event.getY());
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_POINTER_UP:
                // ▼ 判斷是否是第一個手指
                if (event.getPointerId(event.getActionIndex()) == 0){
                    canDrag = false;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                // 如果存在第一個手指由境,且這個手指的落點在圖片區(qū)域內(nèi)
                if (canDrag) {
                    // ▼ 注意 getX 和 getY
                    int index = event.findPointerIndex(0);
                    // Log.i(TAG, "index="+index);
                    mBitmapMatrix.postTranslate(event.getX(index)-lastPoint.x, event.getY(index)-lastPoint.y);
                    lastPoint.set(event.getX(index), event.getY(index));

                    mBitmapRectF = new RectF(0,0,mBitmap.getWidth(), mBitmap.getHeight());
                    mBitmapMatrix.mapRect(mBitmapRectF);

                    invalidate();
                }
                break;
        }

        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawBitmap(mBitmap, mBitmapMatrix, mDeafultPaint);
    }
}

可以看到猪腕,比起上一個版本,只添加了少量代碼法焰,就變得更加“智能”了怎炊,可以準確識別某一個手指谭企,不會因為手指抬起而認錯手指。


重點注意最后评肆,第一個手指抬起之后债查,圖片并沒有跳躍到第二個手指的位置。

上面的兩個對比示例都精簡到了極致瓜挽,其核心依舊是正確的追蹤某一個手指盹廷,建議大家自己寫一遍體會一下。

我感覺很多人看到這里依舊是不明所以的久橙,一些簡單的東西還好弄俄占,但是復(fù)雜一些,如同時處理多個手指的數(shù)值就有些困難了淆衷,假如說你之前沒有接觸過多點觸控的處理缸榄,此時讓你實現(xiàn)用兩個手指來縮放圖片還是有些困難的。

因為這不僅要追蹤兩個手指的位置祝拯,還要根據(jù)位置變化來計算縮放比例和縮放中心甚带,單單這兩個非常簡單的數(shù)學問題就能難倒一大批人。



當然了佳头,很多麻煩問題都有簡單的解決方案鹰贵,假如說我們真的要實現(xiàn)一個可以用兩個或者多個手指縮放的控件,何必要自己算呢康嘉,可以嘗試一下 Android 自帶的解決方案:手勢檢測(GestureDetector)砾莱,不僅能自動幫你計算好縮放比例和縮放中心,而且還可以檢測出 單擊凄鼻、長按腊瑟、滑屏 等不同的手勢,不過這就不是本篇的事情了块蚌,以后有時間會寫一下有關(guān)手勢檢測的用法(繼續(xù)挖坑)闰非。

三、總結(jié)

前段時間因為各種事情比較忙峭范,這篇文章也沒時間去寫财松,所以就一直拖到了現(xiàn)在,期間收到不少讀者催更纱控,實在是抱歉了辆毡。今后在會盡量保證穩(wěn)定更新的,爭取盡快把自定義View系列這一個大坑填完甜害。

關(guān)于多點觸控舶掖,個人認為還算一個比較重要的知識點。尤其是隨著 Android 的發(fā)展尔店,很多炫酷的交互操作可能會需要用戶進行拖拽操作眨攘。在進行這類操作的時候進行一下手指的判斷還是相當重要的。

本文中需要注意的幾個知識點:

如何兼容 2.0 版本的多點觸控(目前大部分都不需要兼容 2.0 了吧)嚣州。
actionIndex鲫售、pointIndex 與 pointId 的區(qū)別和用法。
如何在多點觸控中正確的追蹤一個手指该肴。

參考資料

MotionEvent

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末情竹,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子匀哄,更是在濱河造成了極大的恐慌秦效,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拱雏,死亡現(xiàn)場離奇詭異棉安,居然都是意外死亡,警方通過查閱死者的電腦和手機铸抑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門贡耽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鹊汛,你說我怎么就攤上這事蒲赂。” “怎么了刁憋?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵滥嘴,是天一觀的道長。 經(jīng)常有香客問我至耻,道長若皱,這世上最難降的妖魔是什么镊叁? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮走触,結(jié)果婚禮上晦譬,老公的妹妹穿的比我還像新娘。我一直安慰自己互广,他們只是感情好敛腌,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著惫皱,像睡著了一般像樊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上旅敷,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天生棍,我揣著相機與錄音,去河邊找鬼扫皱。 笑死足绅,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的氢妈。 我是一名探鬼主播段多,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼进苍,長吁一口氣:“原來是場噩夢啊……” “哼觉啊!你這毒婦竟也來了杠人?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎各吨,沒想到半個月后袁铐,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡伪嫁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了似舵。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片葱峡。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡砰奕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出军援,到底是詐尸還是另有隱情胸哥,我是刑警寧澤空厌,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布嘲更,位于F島的核電站赋朦,受9級特大地震影響北发,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瞭恰,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一惊畏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧颜启,春花似錦偷俭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽口猜。三九已至,卻和暖如春济炎,著一層夾襖步出監(jiān)牢的瞬間川抡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工崖堤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人耐床。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓密幔,卻偏偏與公主長得像咙咽,于是被迫代替她去往敵國和親老玛。 傳聞我的和親對象是個殘疾皇子蜡豹,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容