1. 程式人生 > >手勢檢測GestureDetector的實現原理

手勢檢測GestureDetector的實現原理

    public GestureDetector(Context context, OnGestureListener listener, Handler handler) {
        if (handler != null) {
            mHandler = new GestureHandler(handler);
        } else {
            mHandler = new GestureHandler();
        }
        mListener = listener;
        if (listener instanceof OnDoubleTapListener) {
            setOnDoubleTapListener((OnDoubleTapListener) listener);
        }
        init(context);
    }

首先簡單說明一下觸控式螢幕事件的實現過程:觸控式螢幕驅動檢測到MotionEvent——>MotionEvent傳遞到Activity程序(這裡可能還設計到Wms對事件的分發,然後才到Activity)——>Activity.dispatchTouchEvent(MotionEvent)(此處略去Fragment不談)

Activity.dispatchTouchEvent()

    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;//如果該事件已經在Wms中被消化掉了,那麼將不再往下分發
        }
        return onTouchEvent(ev);
    }

    public boolean onTouchEvent(MotionEvent event) {
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }
        
        return false;
    }

dispatchTouchEvent攔截事件,交給onTouchEvent消化處理,我們如果想在Activity的繼承類中消化掉事件,只需要複寫onTouchEvent()方法,並且return true即可。

——>若在上一步中,Activity並沒有攔截MotionEvent,那麼事件將交給View處理(此處先不討論View和ViewGroup之間的事件分發)。這也就是為什麼在View中定義了那麼多可能的事件監聽器監聽處理各種可能的MotionEvent和組合的MotionEvent,因為最終事件往往是交給View進行處理。

再來說說MotionEvent這個類,檢視這個類的原始碼發現它裡面有許多native方法,使用者在螢幕上的各種操作,都由驅動檢測到,然後通過本地程式傳遞到MotionEvent。通過MotionEvent提供的各種變數和方法,我們可以得到使用者操作的各種引數,包括在螢幕上的位置,有幾個手指操作等。View裡的各個監聽器則定義了對一些簡單操作比如單擊事件(onClickListener)的處理。當然我們也可以自定義一些複雜事件,這需要組合多個MotionEvent,即同時檢測到MotionEvent裡定義的多個操作。而GestureDetector就是這麼做的。

在MotionEvent中定義了一些常用事件:

主要的事件型別有:

 ACTION_DOWN: 表示使用者開始觸控.

 ACTION_MOVE: 表示使用者在移動(手指或者其他)

 ACTION_UP:表示使用者擡起了手指 

還有一個不常見的:

ACTION_OUTSIDE: 表示使用者觸碰超出了正常的UI邊界.

但是對於多點觸控的支援,Android加入了以下一些事件型別.來處理,如另外有手指按下了,

有的手指擡起來了.等等:

ACTION_POINTER_DOWN:有一個非主要的手指按下了.

ACTION_POINTER_UP:一個非主要的手指擡起來了

 (2)事件發生的位置,x,y軸

   getX() 獲得事件發生時,觸控的中間區域在螢幕的X軸.

   getY() 獲得事件發生時,觸控的中間區域在螢幕的X軸.

 在多點觸控中還可以通過:    

 getX(int pointerIndex) ,來獲得對應手指事件的發生位置. 獲得Y軸用getY(int pointerIndex)

 (3)其他屬性

  getEdgeFlags():當事件型別是ActionDown時可以通過此方法獲得,手指觸控開始的邊界. 如果是的話,有如下幾種值:EDGE_LEFT,EDGE_TOP,EDGE_RIGHT,EDGE_BOTTOM

而這些事件型別的定義在原生代碼中實現,通過getAction()我們可以獲取到使用者發生了哪些事件型別。
    public final int getAction() {
        return nativeGetAction(mNativePtr);
    }


檢視GestureDetector.java原始碼:

    public GestureDetector(Context context, OnGestureListener listener, Handler handler) {
        if (handler != null) {
            mHandler = new GestureHandler(handler);
        } else {
            mHandler = new GestureHandler();
        }
        mListener = listener;
        if (listener instanceof OnDoubleTapListener) {
            setOnDoubleTapListener((OnDoubleTapListener) listener);
        }
        init(context);
    }
GestureDetector是與Context相關的,即它可以在Activity的onTouchEvent中呼叫處理,因為View也是Context相關的,因此它也可以在View的onTouchEvent中處理。同理,它也使用了一個監聽器來監聽GestureDetector中拓展的一些事件。這裡額外說一點,Java中的監聽介面是一種回撥機制,它實現了控制反轉的功能。下面我們結合onTouchEvent中對事件的處理具體說說控制反轉功能如何實現了我們想要的某種手勢事件。

GestureDetector.onTouchEvent():

public boolean onTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 0);
        }

        final int action = ev.getAction();//獲取使用者發生的事件型別

        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(ev);

        final boolean pointerUp =
                (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP;
        final int skipIndex = pointerUp ? ev.getActionIndex() : -1;

        // Determine focal point
        float sumX = 0, sumY = 0;
        final int count = ev.getPointerCount();
        for (int i = 0; i < count; i++) {
            if (skipIndex == i) continue;
            sumX += ev.getX(i);
            sumY += ev.getY(i);
        }
        final int div = pointerUp ? count - 1 : count;
        final float focusX = sumX / div;
        final float focusY = sumY / div;

        boolean handled = false;

        switch (action & MotionEvent.ACTION_MASK) {//處理使用者發生的事件,所有手勢都是這些事件的簡單或複雜組合
        case MotionEvent.ACTION_POINTER_DOWN:
            mDownFocusX = mLastFocusX = focusX;
            mDownFocusY = mLastFocusY = focusY;
            // Cancel long press and taps
            cancelTaps();
            break;

        case MotionEvent.ACTION_POINTER_UP:
            mDownFocusX = mLastFocusX = focusX;
            mDownFocusY = mLastFocusY = focusY;

            // Check the dot product of current velocities.
            // If the pointer that left was opposing another velocity vector, clear.
            mVelocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
            final int upIndex = ev.getActionIndex();
            final int id1 = ev.getPointerId(upIndex);
            final float x1 = mVelocityTracker.getXVelocity(id1);
            final float y1 = mVelocityTracker.getYVelocity(id1);
            for (int i = 0; i < count; i++) {
                if (i == upIndex) continue;

                final int id2 = ev.getPointerId(i);
                final float x = x1 * mVelocityTracker.getXVelocity(id2);
                final float y = y1 * mVelocityTracker.getYVelocity(id2);

                final float dot = x + y;
                if (dot < 0) {
                    mVelocityTracker.clear();
                    break;
                }
            }
            break;

        case MotionEvent.ACTION_DOWN:
            if (mDoubleTapListener != null) {
                boolean hadTapMessage = mHandler.hasMessages(TAP);
                if (hadTapMessage) mHandler.removeMessages(TAP);
                if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) && hadTapMessage &&
                        isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {
                    // This is a second tap
                    mIsDoubleTapping = true;
                    // Give a callback with the first tap of the double-tap
                    handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);//呼叫mDoubleTapListener.onDoubleTap()處理“雙擊的第一次單擊事件”:首先我們談談為什麼這裡有了“雙擊的第一次單擊事件”,這一切都由if條件判斷決定,當第一個ACTION_DOWN事件傳來時,不會走這個條件語句,因為
mCurrentDownEvent和mPreviousUpEvent的值還為空,從這兩個變數事件賦值的地方來看,當他倆的值不為空的時候,已經經歷了ACTION_DOWN和ACTION_UP兩次事件。因此此時是滿足“第一次單擊事件”的,而再通過
hadTapMessage && isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)這個判斷滿足“雙擊事件”,因此就符合了"雙擊的第一次單擊事件"這個條件。此時有個疑問,這個操作與View中定義的onClick單擊事件處理起來會不會有衝突呢?這我們檢視
isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)可以發現他是有一個時間上的判斷的,即雙擊的間隔不大於多少就是“雙擊的第一次單擊事件”。另外我們討論“控制反轉”這個功能是怎麼通過回撥監聽實現的,首先,在GestureDetector框架裡,有兩個監聽介面————onGestureListener和onDoubleTapListener,在框架裡需要實現事件操作,比如說本處的“雙擊的第一次單擊事件”操作時呼叫上述接口裡的回撥函式,在框架裡,該函式裡是沒有實現任何操作的,但是程式設計師在呼叫這個框架的時候,需要實現這些介面,此時這裡的
mDoubleTapListener是我們自己實現該介面後的物件,並且對這些介面函式進行了複寫,有了實際的內容。即在框架中只定義介面和回撥函式,呼叫的也是該介面物件,而真正賦值過來的卻是該介面的實現類,回撥函式也有了內容。

// Give a callback with down event of the double-tap handled |= mDoubleTapListener.onDoubleTapEvent(ev); } else { // This is a first tap mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT); } } mDownFocusX = mLastFocusX = focusX; mDownFocusY = mLastFocusY = focusY; if (mCurrentDownEvent != null) { mCurrentDownEvent.recycle(); } mCurrentDownEvent = MotionEvent.obtain(ev);//mCurrentDownEvent第一次賦值的地方 mAlwaysInTapRegion = true; mAlwaysInBiggerTapRegion = true; mStillDown = true; mInLongPress = false; if (mIsLongpressEnabled) { mHandler.removeMessages(LONG_PRESS); mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime() + TAP_TIMEOUT + LONGPRESS_TIMEOUT); } mHandler.sendEmptyMessageAtTime(SHOW_PRESS, mCurrentDownEvent.getDownTime() + TAP_TIMEOUT); handled |= mListener.onDown(ev); break; case MotionEvent.ACTION_MOVE: if (mInLongPress) { break; } final float scrollX = mLastFocusX - focusX; final float scrollY = mLastFocusY - focusY; if (mIsDoubleTapping) { // Give the move events of the double-tap handled |= mDoubleTapListener.onDoubleTapEvent(ev); } else if (mAlwaysInTapRegion) { final int deltaX = (int) (focusX - mDownFocusX); final int deltaY = (int) (focusY - mDownFocusY); int distance = (deltaX * deltaX) + (deltaY * deltaY); if (distance > mTouchSlopSquare) { handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); mLastFocusX = focusX; mLastFocusY = focusY; mAlwaysInTapRegion = false; mHandler.removeMessages(TAP); mHandler.removeMessages(SHOW_PRESS); mHandler.removeMessages(LONG_PRESS); } if (distance > mDoubleTapTouchSlopSquare) { mAlwaysInBiggerTapRegion = false; } } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) { handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); mLastFocusX = focusX; mLastFocusY = focusY; } break; case MotionEvent.ACTION_UP: mStillDown = false; MotionEvent currentUpEvent = MotionEvent.obtain(ev); if (mIsDoubleTapping) { // Finally, give the up event of the double-tap handled |= mDoubleTapListener.onDoubleTapEvent(ev); } else if (mInLongPress) { mHandler.removeMessages(TAP); mInLongPress = false; } else if (mAlwaysInTapRegion) { handled = mListener.onSingleTapUp(ev);//同理我們也可以分析出“單指彈起事件” } else { // A fling must travel the minimum tap distance final VelocityTracker velocityTracker = mVelocityTracker; final int pointerId = ev.getPointerId(0); velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity); final float velocityY = velocityTracker.getYVelocity(pointerId); final float velocityX = velocityTracker.getXVelocity(pointerId); if ((Math.abs(velocityY) > mMinimumFlingVelocity) || (Math.abs(velocityX) > mMinimumFlingVelocity)){ handled = mListener.onFling(mCurrentDownEvent, ev, velocityX, velocityY); } } if (mPreviousUpEvent != null) { mPreviousUpEvent.recycle(); } // Hold the event we obtained above - listeners may have changed the original. mPreviousUpEvent = currentUpEvent;//mPreviousUpEvent第一次賦值的地方 if (mVelocityTracker != null) { // This may have been cleared when we called out to the // application above. mVelocityTracker.recycle(); mVelocityTracker = null; } mIsDoubleTapping = false; mHandler.removeMessages(SHOW_PRESS); mHandler.removeMessages(LONG_PRESS); break; case MotionEvent.ACTION_CANCEL: cancel(); break; } if (!handled && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(ev, 0); } return handled; }
總的來說,MotionEvent中定義了最基本的螢幕事件,而Activity,View和GestureDetector在onTouchEvent中消化處理這些事件,並且通過組合這些基本事件,可以得到一些複雜事件,通過監聽這些複雜事件,可以實現一些複雜操作。