android自定義View之多點觸控

最近在看PhotoView的源碼,里面涉及到View事件的多點觸控部分知識,在看了一些資料后,先做個簡單的總結。


基本知識點

  • 多點觸控的事件同樣是在View的onTouchEvent()方法中得到回調
  • 多點觸控的基本事件類型需要通過event.getActionMasked()獲取
  • 獲取到的基本事件類型包括:
    • ACTION_DOWN:第一個手指初次接觸到屏幕時觸發
    • ACTION_POINTER_DOWN:有非主要的手指按下(即按下之前已經有手指在屏幕上
    • ACTION_UP:最后一個 手指離開屏幕時觸發。
    • ACTION_POINTER_UP:有非主要的手指抬起(即抬起之后仍然有手指在屏幕上)
    • ACTION_MOVE:手指在屏幕上滑動時觸發
  • 不同于單點觸控,多點觸摸時要獲取到觸摸點的坐標,需要判斷觸摸的是哪個點:event.getX(int pointerIndex)。這個pointerIndex就是觸摸點的序號
  • 燃鵝,這個pointerIndex有點貓膩:它會在幾個手指的抬起放下的過程中發生變化。GcsSloop的文章里在解釋這個點的時候有點復雜化了,其實可以用一個非常簡單的例子來說明:
    我們常用的ArrayList,它就是一個鏈表結構,手指放下(觸摸屏幕)時,就相當于在list中add一個觸摸點,這個點在list中的index就是pointerIndex。抬起(刪除)再添加(add)pointerIndex都跟著變化。
    就是這么簡單
  • 但是,如果單靠pointerIndex來記錄觸摸點,那在不斷抬起按下的過程中,這么多變化,維護起來就太麻煩了,因此還有一個pointerId,它是一個觸摸點在整個從按下到抬起過程中,唯一不變的id。它也并不是一直增長的,而是如果在中間有觸摸點抬起了,那個點的id就會等待被分配給下一根手指的觸摸點。
    舉個栗子:
    • 依次按下0-1-2三根手指,pointerIndex和pointerId一樣都依次為0-1-2,
    • 這時抬起1,pointerIndex:0-1,pointerId:0-2,
    • 再按下一根手指,pointerIndex:0-1-2,pointerId:0-2-1。
  • 由于pointerId在整個觸摸過程中保持不變,所以一般用它來區分是哪根手指。
    pointerIndex可以轉化為pointerId:getPointerId(int pointerIndex),
    pointerId也可以轉化為pointerIndex:findPointerIndex(int pointerId)

SHOW ME THE CODE

最后以一個簡單的在多點觸控中追蹤單個手指的栗子來直觀地加深印象:

/**
 * 繪制出第二個手指第位置
 */
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 來取出對應的坐標
                    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);
        }
    }
}

(代碼來自GcsSloop的文章


后記

以上是一個簡單的總結以備知識點速查。
下一篇文章將是PhotoView的源碼分析,以及仿微信朋友圈大圖查看(在列表中點擊小圖移動到大圖中心、進入時先顯示縮略圖、加載完成后自動擴大到大圖、圖片放大、點擊恢復原圖、向下拖拽大圖縮小退出到列表小圖中)

參考:
Google的官方文檔
GcsSloop的相關文章

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容