Android 觸控事件處理機制

KingsLanding發表於2015-06-15

  Android 觸控事件的處理主要涉及到幾個方法:onInterceptTouchEvent(), dipatchTouchEvent(), onTouchEvent(), onTouch()。

  onInterceptTouchEvent() 用於攔截事件並改變事件傳遞方向。解釋一下事件傳遞。比如一個Activity中展示給使用者可能是ViewGroup和View的多層巢狀,預設情況下觸控事件產生之後從最外層一次傳遞到最裡面一層,然後在從最裡面一層開始響應。從最裡面一層開始依次呼叫各層次的dispatchTouchEvent()進行分發,dispatchTouchEvent()中在呼叫onTouch / onTouchEvent進行響應觸控事件。

  onInterceptTouchEvent() 方法可以將觸控事件的傳遞截斷,讓觸控事件在某一層就不往下面傳遞,就開始呼叫這一層的dispatchTouchEvent(),開始向上層返回。如果需要在某一層攔截,需要複寫該層的onInterceptTouchEvent()方法,並讓該方法返回 true。通過一張圖來解釋一下。

  

  假設一個Activity展示的介面有A->B->C->D四層,當事件發生之後,首先經過A的onInterceptTouchEvent(), 如果A的onInterceptTouchEvent()返回false,則會傳遞到B的onInterceptTouchEvent(),如果返回false則一次向下(內層)傳遞。如果某一層的onInterceptTouchEvent()返回true,然後就會呼叫該層的disatchTouchEvent()分發事件,事件不再向下傳遞。如果各層都沒有攔截事件則從最內層開始呼叫dispatchTouchEvent(),如果某一各層的dispatchTouchEvent()返回true,則表明該層消費了該事件,則上面層的dispatchTouEvent()不會被呼叫。舉一個例子:

  

  上圖中B層的onInterceptTouchEvent()返回true,則事件被攔截,開始呼叫B層的dispatchTouchEvent()向上返回一次響應觸控事件。

  知道這個機制有什麼卵用嗎?

  一個簡單例子,我們在scrollView中放置了圖片,圖片允許縮放拖動,但是你對圖片進行拖動的時候會發現scrollView也跟著動,這樣體驗就會很不好,怎麼辦呢?

  結合上面的分析,我們可以知道,如果讓觸控事件傳遞到內層的圖片,然後在在圖片的disPatchTouchEvent()中把這個觸控事件消費了就不就可以了嗎?

  具體做法在圖片的onTouch() 方法中,ACTION_DOWN中設定scrollView不攔截事件,通過scrollView.requestDisallowInterceptTouchEvent(true)來完成,完成想要的處理之後在圖片的onTouch()方法最後返回true就可以實現了。

  問題又來了 onTouch(View v, MotionEvent event) 和 onTouchEvent(MotionEvent event) 有什麼區別呢? 看起來那麼像,不會是完成相同的功能吧?這樣做不是多此一舉嗎,為什麼不用一個就好了。

  為了搞清楚這個問題,首先需要來看View中disPatchTouchEvent()方法:

public boolean dispatchTouchEvent(MotionEvent event) {
    // If the event should be handled by accessibility focus first.
    if (event.isTargetAccessibilityFocus()) {
        // We don't have focus or no virtual descendant has it, do not handle the event.
        if (!isAccessibilityFocusedViewOrHost()) {
            return false;
        }
        // We have focus and got the event, then use normal event dispatch.
        event.setTargetAccessibilityFocus(false);
    }

    boolean result = false;

    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(event, 0);
    }

    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // Defensive cleanup for new gesture
        stopNestedScroll();
    }

    if (onFilterTouchEventForSecurity(event)) {
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }

        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }

    if (!result && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }

    // Clean up after nested scrolls if this is the end of a gesture;
    // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
    // of the gesture.
    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }

    return result;
}

  從上面可以看出onTouch() 先於 onTouchEvent()執行。通過檢視View的原始碼還發現,或者簡單推理一下我們設定setOnTouchListener()設定的是誰就知道,我們什麼時候需要呼叫onTouch()方法讓其發揮作用而不是讓onTouchEvent()來響應了。View原始碼中可以看出onTouch是一個Interface的方式實現,將處理邏輯的實現交給開發者自定義,因此可以得出Android系統自帶的控制元件我們使用onTouch處理事件,如果我們需要擴充套件View,則需要複寫onTouchEvent()來實現事件的處理。其實onTouch(View v, MotionEvent event) 和 onTouchEvent(MotionEvent event)可以從這兩個方法接受的引數就可以看出不同來,onTouch包含一個View型別的引數,因此是可以設定給某個View的,onTouchEvent()則是給某個View自己用的。

  既然提到了View的事件響應,那onClick事件又是怎麼響應的呢? 通過產看原始碼可以發現onClick是在onTouchEvent中執行的,而且是在onTouchEvent的ACTION_UP事件中執行的。因此如果View 的onTouch()返回true則會導致onClick得不到執行,因為onTouchEvent()得不到執行。

  此外Activity中也有onTouchEvent()成員方法,如果Activity中的View都不處理Event則Activity的onTouchEvent()會呼叫。

 

相關文章