事件分發(fā)機(jī)制

為什么會有事件分發(fā)機(jī)制?

Android上的View是樹形結(jié)構(gòu)的,有可能重疊在一起,當(dāng)我們點擊的地方有多個View都可以響應(yīng)的時候,這個點擊事件應(yīng)該給誰呢?為了解決這一問題就有了事件分發(fā)機(jī)制。

相關(guān)方法

  • dispatchTouchEvent() : 分發(fā)事件
  • onInterceptTouchEvent():判斷是否攔截事件
  • onTouchEvent() :處理事件

Activity和View(不能包含子View)是沒有onInterceptTouchEvent()的。Activity是事件最初的接收者,如果一開始就被它攔截了事件分發(fā)也就沒有了意義。View是沒有子View的,所以沒有攔截事件的方法。

源碼分析

1.當(dāng)點擊事件發(fā)生時,首先調(diào)用的是Activity的dispatchTouchEvent()方法,看一下該方法的實現(xiàn):

調(diào)用過程:如果是按下事件就調(diào)用onUserInteraction()方法,改方法是一個空方法,沒有任何實現(xiàn),然后調(diào)用Window的superDispatchTouchEvent(ev)方法,如果該方法返回true,調(diào)用結(jié)束;如果返回false,就調(diào)用Activity的onTouchEvent()方法,自己處理點擊事件。該方法比較簡單。

2.getWindow()返回的是Window(抽象類)的實現(xiàn)類PhoneWindow的對象,進(jìn)而我們找到PhoneWindow的superDispatchTouchEvent(ev)方法。

在該方法中又調(diào)用mDecor的superDispatchTouchEvent(ev)方法。進(jìn)而我們找到mDecor

在該方法中調(diào)用父類的dispatchTouchEvent(ev)方法,DecorView 繼承FrameLayout,而FrameLayout又繼承ViewGroup,所以我們找到ViewGroup的dispatchTouchEvent(ev)方法

到此處我們先小小總結(jié)一下

在Activity的dispatchTouchEvent()方法中會調(diào)用getWindow().superDispatchTouchEvent(ev),其實就是調(diào)用ViewGroup的dispatchTouchEvent(ev)方法。

3.繼續(xù)查看ViewGroup的dispatchTouchEvent(ev)方法。該方法的代碼比較復(fù)雜,我們只分析它的核心部分:

    ........

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;
}

      ..................

if (!canceled && !intercepted) {
      ...........
}

在ViewGroup的dispatchTouchEvent(tv)方法中會調(diào)用onInterceptTouchEvent(ev)方法,如果intercepted的值為true,也就是攔截了該事件。就不會執(zhí)行 if (!canceled && !intercepted) 中的代碼,該區(qū)域的代碼主要是遍歷所有的子View,查看是否攔截與處理。

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

如果是第一次觸摸就會執(zhí)行dispatchTransformedTouchEvent方法,在剛開始的時候mFirstTouchTarget肯定為空。繼而查看dispatchTransformedTouchEvent方法。該方法有四個參數(shù),其中第三個參數(shù)為child,傳遞的值為null。

 if (newPointerIdBits == oldPointerIdBits) {
        if (child == null || child.hasIdentityMatrix()) {
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                final float offsetX = mScrollX - child.mLeft;
                final float offsetY = mScrollY - child.mTop;
                event.offsetLocation(offsetX, offsetY);
                handled = child.dispatchTouchEvent(event);
                event.offsetLocation(-offsetX, -offsetY);
            }
            return handled;
        }
        transformedEvent = MotionEvent.obtain(event);
  } else {
       transformedEvent = event.split(newPointerIdBits);
  }

因為上面?zhèn)鬟f的child為null,所有調(diào)用 handled = super.dispatchTouchEvent(event);就只執(zhí)行View的dispatchTouchEvent(ev)方法,該方法比較簡單。

 ............

 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;
        }
  }

 ...............

在該方法中會判斷,如果mOnTouchListener != null就會調(diào)用mOnTouchListener.onTouch(),并且返回true,下面的onTouchEvent(ev)也就不會再執(zhí)行,這也是為什么給View設(shè)置OnTouchListener后不再調(diào)用onTouch方法。也可以說如果設(shè)置了OnTouchListener,那么View的onTouchEvent(ev)方法就不會再執(zhí)行。

綜上所述:如果上層View不攔截,事件就會一直傳遞到View中,調(diào)用View的dispatchTouchEvent(ev)方法,如果設(shè)置了OnTouchListener,View的onTouchEvent(ev)方法就不會執(zhí)行了,事件就此消耗,也不會回傳給父類了。如果沒有設(shè)置OnTouchListener,就會調(diào)用View的onTouchEvent(ev)方法。

你應(yīng)該知道的

通過對事件分發(fā)機(jī)制源碼的閱讀和了解,你應(yīng)該知道的:

  • 1.在ViewGroup中重寫onInterceptTouchEvent方法返回true為什么會攔截事件,并且該ViewGroup會消費了Event(調(diào)用onOnTouchEvent)。
  • 2.為什么事件不再向子控件繼續(xù)傳遞?
  • 3.當(dāng)父控件沒有攔截事件時,事件是如何傳遞到子控件的
  • 4.點擊事件中的x,y坐標(biāo)值都是以父布局的相對坐標(biāo),這里又是如何一層一層轉(zhuǎn)換的
  • 5.為什么給自定義View設(shè)置OnTouchListener后不再調(diào)用 重寫的onTouch方法

總結(jié)

Android的時間分發(fā)機(jī)制還是比較復(fù)雜的,尤其是ViewGroup的dispatchTouchEvent(ev)方法,我們可以通過閱讀源碼來了解它的實現(xiàn)原理。其實在項目開發(fā)中我們不必要非常清楚它的實現(xiàn)原理,也能解決時間沖突問題。只需要用好相關(guān)的方法即可( dispatchTouchEvent() ,onInterceptTouchEvent(),onTouchEvent() )但是如果能清楚了了解它的實現(xiàn)原理,那么在遇到時間沖突問題時,更容易解決。拋開事件分發(fā)本身而言,它的代碼設(shè)計也值得我們學(xué)習(xí),這里面用到了責(zé)任鏈模式,雖然它們沒有繼承共同的類,但是它們都有共同的方法(dispatchTouchEvent)。其實通過源碼分析我們只僅僅能解決問題,還能學(xué)習(xí)到更多的東西。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容