為什么會有事件分發(fā)機(jī)制妒蔚?
Android上的View是樹形結(jié)構(gòu)的,有可能重疊在一起月弛,當(dāng)我們點(diǎn)擊的地方有多個View都可以響應(yīng)的時候肴盏,這個點(diǎn)擊事件應(yīng)該給誰呢?為了解決這一問題就有了事件分發(fā)機(jī)制帽衙。
相關(guān)方法
- dispatchTouchEvent() : 分發(fā)事件
- onInterceptTouchEvent():判斷是否攔截事件
- onTouchEvent() :處理事件
Activity和View(不能包含子View)是沒有onInterceptTouchEvent()的菜皂。Activity是事件最初的接收者,如果一開始就被它攔截了事件分發(fā)也就沒有了意義厉萝。View是沒有子View的恍飘,所以沒有攔截事件的方法。
源碼分析
1.當(dāng)點(diǎn)擊事件發(fā)生時谴垫,首先調(diào)用的是Activity的dispatchTouchEvent()方法章母,看一下該方法的實(shí)現(xiàn):
調(diào)用過程:如果是按下事件就調(diào)用onUserInteraction()方法,改方法是一個空方法翩剪,沒有任何實(shí)現(xiàn)乳怎,然后調(diào)用Window的superDispatchTouchEvent(ev)方法,如果該方法返回true,調(diào)用結(jié)束前弯;如果返回false蚪缀,就調(diào)用Activity的onTouchEvent()方法,自己處理點(diǎn)擊事件博杖。該方法比較簡單椿胯。
2.getWindow()返回的是Window(抽象類)的實(shí)現(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),其實(shí)就是調(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);
}
因?yàn)樯厦鎮(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會消費(fèi)了Event(調(diào)用onOnTouchEvent)瘪匿。
- 2.為什么事件不再向子控件繼續(xù)傳遞跛梗?
- 3.當(dāng)父控件沒有攔截事件時,事件是如何傳遞到子控件的
- 4.點(diǎn)擊事件中的x,y坐標(biāo)值都是以父布局的相對坐標(biāo)柿顶,這里又是如何一層一層轉(zhuǎn)換的
- 5.為什么給自定義View設(shè)置OnTouchListener后不再調(diào)用 重寫的onTouch方法
總結(jié)
Android的時間分發(fā)機(jī)制還是比較復(fù)雜的茄袖,尤其是ViewGroup的dispatchTouchEvent(ev)方法,我們可以通過閱讀源碼來了解它的實(shí)現(xiàn)原理嘁锯。其實(shí)在項(xiàng)目開發(fā)中我們不必要非常清楚它的實(shí)現(xiàn)原理,也能解決時間沖突問題聂薪。只需要用好相關(guān)的方法即可( dispatchTouchEvent() 家乘,onInterceptTouchEvent(),onTouchEvent() )但是如果能清楚了了解它的實(shí)現(xiàn)原理藏澳,那么在遇到時間沖突問題時仁锯,更容易解決。拋開事件分發(fā)本身而言翔悠,它的代碼設(shè)計(jì)也值得我們學(xué)習(xí)滤港,這里面用到了責(zé)任鏈模式健田,雖然它們沒有繼承共同的類,但是它們都有共同的方法(dispatchTouchEvent)。其實(shí)通過源碼分析我們只僅僅能解決問題锐借,還能學(xué)習(xí)到更多的東西。