Android事件分發攔截以及處理分析

在Android開發中,經常遇到如下場景,在一個ViewGroup中嵌套有其它ViewGroup或者View,這時點擊被嵌套的ViewGroup或者View,這時點擊事件到底是怎麼被處理的呢?下面就以下面的嵌套爲例子來說明
這裏寫圖片描述

從圖中可以看出CustomGroupA、CustomGroupB以及CustomView三者之間的嵌套關係,爲了說明點擊CustomView後,事件最終被處理的情況,我們必須從自定義控件的dispatchTouchEvent、onInterceptTouchEvent以及onTouchEvent這三巨頭說起。

1、自定義控件的三巨頭方法

如上所述,這裏的三巨頭就是指自定義控件的dispatchTouchEvent、onInterceptTouchEvent以及onTouchEvent這三個方法了,dispatchTouchEvent是啓動方法,在該方法中完成對重載onInterceptTouchEvent以及onTouchEvent這兩個重載方法的調用,所以一般而言在自定義控件的時候基本就是用默認的dispatchTouchEvent重載方法,而不作改動。

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    Log.d(TAG,"CustomGroupB: dispatchTouchEvent");
    return super.dispatchTouchEvent(ev);
}

主線程在捕獲到點擊事件後,會首先調用最外層的ViewGroup的dispatchTouchEvent方法,並通過判斷ViewGroup有沒有子控件來調用子控件的dispatchTouchEvent方法,這就形成了一個dispatchTouchEvent的調用樹。當然是否調用子控件的dispatchTouchEvent方法就要取決於這裏的另一巨頭方法了,就是onInterceptTouchEvent這個方法,當onInterceptTouchEvent方法返回false時,不攔截事件,於是外層ViewGroup就會調用子控件的dispatchTouchEvent方法,反之就不會。這裏點可以從ViewGroup方法的dispatchTouchEvent方法分析出來,這裏貼出部分代碼

// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
        || mFirstTouchTarget != null) {
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    if (!disallowIntercept) {
        intercepted = onInterceptTouchEvent(ev);
        ev.setAction(action); // restore action in case it was changed
    } else {
        intercepted = false;
    }
} else {
    // There are no touch targets and this action is not an initial down
    // so this view group continues to intercept touches.
    intercepted = true;
}

當onInterceptTouchEvent返回false時,intercepted也就是false,那麼之後的如下判斷語句纔有可能爲true

if (!canceled && !intercepted)

繼而獲取子控件並執行子控件的dispatchTouchEvent方法,所以我們這裏可以分析出來如果上面的CustomGroupA、CustomGroupB的onInterceptTouchEvent方法都返回false的話(需要注意View沒有子控件,所以也就沒有這裏所謂的onInterceptTouchEvent攔截重載方法了),那麼這裏的方法執行順序就應該是如下

CustomGroupA: dispatchTouchEvent --> CustomGroupA:onInterceptTouchEvent
CustomGroupB: dispatchTouchEvent --> CustomGroupB: onInterceptTouchEvent

而還一個三巨頭即onTouchEvent方法什麼時候執行呢,我們上面說了dispatchTouchEvent 形成了一個嵌套樹了,那麼直到執行到晚最底層控件的dispatchTouchEvent方法後,纔會返回去繼續執行上層ViewGroup控件未執行完的dispatchTouchEvent方法,所以這裏就直接看最底層CustomView的dispatchTouchEvent方法

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    Log.d(TAG,"CustomView: dispatchTouchEvent");
    return super.dispatchTouchEvent(event);
}

這裏直接調用到了View父控件的dispatchTouchEvent方法,於是我們來看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)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        //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;
}

這裏在判斷的過程中,調用三巨頭之一的onTouchEvent方法,於是這裏我們知道處在最下層的自定義View的onTouch重載方法最新被調用。並且當onTouchEvent方法返回false的時候,View: dispatchTouchEvent可能返回true,不然就返回false了

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

CustomView的dispatchTouchEvent方法調用完成後,繼續執行CustomGroupB的dispatchTouchEvent方法,並且根據CustomVIew的dispatchTouchEvent方法返回值來進一步的判斷是否需要繼續執行CustomGroupB的返回值,當CustomVIew: dispatchTouchEvent返回false時,ViewGroup: dispatchTouchEvent的如下代碼條件成立

if (mFirstTouchTarget == null) {
    // No touch targets so treat this as an ordinary view.
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
} 

繼續執行dispatchTransformedTouchEvent方法,並在該方法中執行到View的onDispatchTouchEvent方法中去,並調用CustomGroupB: onTouchEvent方法

if (child == null) {
    handled = super.dispatchTouchEvent(event);
} 

當CustomVIew: dispatchTouchEvent返回true時,if (mFirstTouchTarget == null) 條件不成立,不再繼續執行ViewGroupB: onTouch方法,並且CustomGroupB: dispatchTouchEvent也返回true那麼同理CustomGroupA: onTouchEvent方法也不會執行。這就是三巨頭之一的onTouchEvent的作用了,在onTouchEvent重載方法中對事件進行處理,並且通過返回值決定該事件是否傳給上層控件繼續處理。

這裏總結一下三巨頭方法的作用

1、dispatchTouchEvent:觸發父控件(ViewGroup或View)的dispatchTouchEvent方法,並在該方法中判斷是否對事件進行攔截不傳給底層控件,或者底層控件在處理事件後,進行判斷是否繼續講事件交給上層控件處理;

2、onInterceptTouchEvent:上層控件通過該方法返回值判斷,是否攔截事件,返回true攔截事件,false不攔截,在攔截事件後,底層控件的dispatchTouchEvent便沒有機會執行,也就更沒有機會執行onInterceptTouchEvent以及onTouchEvent方法了;

3、onTouchEvent:對事件進行處理,並根據返回值判斷是否傳遞事件給上層控件處理,返回false繼續上傳給上層控件處理,返回true便不上傳,當不上傳時,所有上層控件便都沒有機會執行onTouchEvent處理該事件;

通過以上總結我們可以推斷出來,如果重寫dispatchTouchEvent,並不添加super.dispatchTouchEvent(ev)代碼,那麼對應控件的onInterceptTouchEvent以及onTouchEvent便無法得到執行,那麼它的下層控件也將無法繼續執行三巨頭方法,上層控件會根據dispatchTouchEvent重寫方法來判斷是否繼續執行onTouchEvent方法,當dispatchTouchEvent返回true時,上層控件不再執行onTouchEvent方法,當dispatchTouchEvent返回false時,上層控件繼續執行onTouchEvent方法。

2、結論驗證

這一小節,我們來對第一小節的源碼分析進行驗證,首先重寫CustomGroupA、CustomGroupB以及CustomView的三巨頭方法返回值分別如下

function CustomGroupA CustomGruopB CustomView
dispatchTouchEvent 默認 默認 默認
onInterceptTouchEvent false fasle 無重載方法
onTouchEvent false false false

按照之前的理論,這裏的三個自定義控件都不攔截事件,並且底層控件都將事件處理後交給上層控件處理,那麼我們來看看針對不同控件的三巨頭方法寫的日誌的執行情況如下

06-03 14:11:04.546 6512-6512/com.yjing.example D/yjing: CustomGroupA: dispatchTouchEvent
    CustomGroupA: onInterceptTouchEvent
06-03 14:11:04.547 6512-6512/com.yjing.example D/yjing: CustomGroupB: dispatchTouchEvent
    CustomGroupB: onInterceptTouchEvent
    CustomView: dispatchTouchEvent
    CustomView: onTouchEvent
    CustomGroupB: onTouchEvent
    CustomGroupA: onTouchEvent

這就證實了我們上面的分析。

再看另一個情況,如下表所示

function CustomGroupA CustomGruopB CustomView
dispatchTouchEvent 默認 默認 默認
onInterceptTouchEvent false true 無重載方法
onTouchEvent false false false

這裏我們在CustomGroupB控件中對時間進行了攔截,那麼按照分析,CustomView將沒有機會執行三巨頭方法了,我們來看日誌

06-03 14:07:53.545 6284-6284/com.yjing.example D/yjing: CustomGroupA: dispatchTouchEvent
    CustomGroupA: onInterceptTouchEvent
    CustomGroupB: dispatchTouchEvent
    CustomGroupB: onInterceptTouchEvent
    CustomGroupB: onTouchEvent
06-03 14:07:53.546 6284-6284/com.yjing.example D/yjing: CustomGroupA: onTouchEvent

下面我再看看onTouchEvent方法返回true的情況

function CustomGroupA CustomGruopB CustomView
dispatchTouchEvent 默認 默認 默認
onInterceptTouchEvent false false 無重載方法
onTouchEvent false false true

按照分析,這種情況下CustomGroupA以及CustomGroupB的onTouchEvent方法都不會有機會處理事件了,我們看日誌

06-03 14:06:27.271 6046-6046/com.yjing.example D/yjing: CustomGroupA: dispatchTouchEvent
    CustomGroupA: onInterceptTouchEvent
    CustomGroupB: dispatchTouchEvent
    CustomGroupB: onInterceptTouchEvent
    CustomView: dispatchTouchEvent
    CustomView: onTouchEvent

最後,我們看看dispatchTouchEvent方法不用默認方法的情況,我們改動CustomGroupB的dispatchTouchEvent重載方法如下

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    Log.d(TAG,"CustomGroupB: dispatchTouchEvent");
    //return super.dispatchTouchEvent(ev);
    return true;
}

然後三巨頭情況如下

function CustomGroupA CustomGruopB CustomView
dispatchTouchEvent 默認 重寫 默認
onInterceptTouchEvent false fasle 無重載方法
onTouchEvent false false false

按照上面分析CustomGroupB: dispatchTouchEvent直接返回true那麼,CustomGroupA的onTouchEvent方法沒有機會執行了,並且由於CustomGroupB: dispatchTouchEvent重載方法沒調用super.dispatchTouchEvent(ev);那麼CustomGroupB的onInterceptTouchEvent以及onTouchEvent這兩巨頭方法也不會執行,CustomView的三巨頭方法都不會執行了,我們來看日誌是不是這樣

06-03 14:18:33.624 6888-6888/com.yjing.example D/yjing: CustomGroupA: dispatchTouchEvent
    CustomGroupA: onInterceptTouchEvent
    CustomGroupB: dispatchTouchEvent

有日誌可以看出推論正確,所以我們看到dispatchTouchEvent重載方法沒調用super.dispatchTouchEvent(ev),那麼當前控件以及其下層的控件的三巨頭方法都失效了,所以我們一般會使用默認的dispatchTouchEvent方法。

以上就是Android事件的分發(dispatchTouchEvent)、攔截(onInterceptTouchEvent)以及處理(onTouchEvent)分析,但然這裏只是結合源碼進行了粗略的分析總結,以僅僅來加強理解而已,望童鞋們多多交流,謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章