一、引言
View的事件分發(fā)一直都是塊難啃的骨頭,每次都是在遇到問(wèn)題時(shí)才在網(wǎng)上找一下事件分發(fā)的流程宾毒,而每次看的時(shí)候當(dāng)時(shí)都以為懂了,但是過(guò)了一段時(shí)間卻又忘了殿遂。如此反復(fù)诈铛,對(duì)View的事件分發(fā)一直沒(méi)有搞懂乙各,所以這次決定結(jié)合源碼來(lái)分析View的事件分發(fā)機(jī)制。
二幢竹、源碼分析
在進(jìn)行源碼分析前耳峦,我們先來(lái)看一下View的事件分發(fā)的流程圖。
看一下這個(gè)事件分發(fā)流程圖焕毫,是不是覺(jué)得View的事件分發(fā)也沒(méi)有那么復(fù)雜吧蹲坷。
下面我們開(kāi)始通過(guò)源碼來(lái)驗(yàn)證上面流程的正確性,Let‘s go~
2.1 Activity對(duì)事件進(jìn)行分發(fā)
當(dāng)一個(gè)點(diǎn)擊事件發(fā)生時(shí)邑飒,事件最先傳遞到當(dāng)前View所在的Activity中循签,由Activity的dispatchTouchEvent進(jìn)行分發(fā)。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
首先調(diào)用了getWindow(). superDispatchTouchEvent方法幸乒,返回true表示事件被消耗掉了懦底;返回false表示事件交給Activity的onTouchEvent方法處理。
getWindow()返回的是Window類(lèi)罕扎。在Android中聚唐,只有PhoneWindow是Window的唯一實(shí)現(xiàn)類(lèi),所以執(zhí)行的正是PhoneWindow里的方法腔召。
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
在PhoneWindow中杆查,也沒(méi)有復(fù)雜的處理邏輯,轉(zhuǎn)而調(diào)用了變量mDecor的superDispatchTouchEvent方法臀蛛。
這里的mDecor是DecorView類(lèi)型亲桦。DecorView我們挺熟悉了,它是Activity的頂級(jí)View浊仆。在Activity中客峭,我們通過(guò)setContentView方法設(shè)置了自定義View,而DecorView就是我們自定義View的父容器抡柿。
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
DecorView對(duì)于superDispatchTouchEvent方法的實(shí)現(xiàn)邏輯就更奇葩了舔琅,直接調(diào)用父類(lèi)的dispatchTouchEvent方法,而DecorView的父類(lèi)是ViewGroup洲劣。
注意了备蚓,事件這里已經(jīng)傳遞到了第一個(gè)頂級(jí)ViewGroup的dispatchTouchEvent方法了。
2.2 ViewGroup對(duì)事件進(jìn)行分發(fā)
ViewGroup中的dispatchTouchEvent方法的處理邏輯比較復(fù)雜囱稽,那我們就化整為零郊尝,一段一段的看。
public boolean dispatchTouchEvent(MotionEvent ev) {
...省略
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//由于事件為ACTION_DOWN時(shí)战惊,重置了mGroupFlags流昏,所以一定會(huì)調(diào)用onInterceptTouchEvent方法。
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//調(diào)用onInterceptTouchEvent方法,判斷是否需要攔截該事件
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
//當(dāng)事件不為ACTION_DOWN横缔,而且mFirstTouchTarget == null時(shí)铺遂,intercepted置為true。
intercepted = true;
}
...省略
}
actionMasked == MotionEvent.ACTION_DOWN茎刚,我們比較好理解襟锐,表示按下事件。
但是mFirstTouchTarget != null膛锭,是什么意思呢粮坞?
其實(shí)看完下面的代碼就會(huì)知道,當(dāng)點(diǎn)擊事件被ViewGroup中的子View消費(fèi)后初狰,那么mFirstTouchTarget != null莫杈。反過(guò)來(lái)說(shuō),當(dāng)點(diǎn)擊事件被當(dāng)前ViewGroup消費(fèi)了奢入,那么mFirstTouchTarget == null筝闹。
我們知道,事件的最開(kāi)始一定是ACTION_DOWN事件腥光,然后伴隨著一系列的ACTION_MOVE事件关顷,最后是ACTION_UP事件。
當(dāng)事件為ACTION_DOWN時(shí)武福,mGroupFlags總是被重置了议双。
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
private void resetTouchState() {
clearTouchTargets();
resetCancelNextUpFlag(this);
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
mNestedScrollAxes = SCROLL_AXIS_NONE;
}
當(dāng)事件為ACTION_DOWN時(shí),由于mGroupFlags的重置捉片,所以disallowIntercept的值為false平痰,也就是說(shuō)onInterceptTouchEvent方法一定會(huì)執(zhí)行。
注意了伍纫,事件這里已經(jīng)傳遞到了第一個(gè)頂級(jí)ViewGroup的onInterceptTouchEvent方法了宗雇。
onInterceptTouchEvent方法的返回值決定著事件分發(fā)的走向,分為如下兩個(gè)分支:
1莹规、當(dāng)返回為true時(shí)逾礁,表示當(dāng)前ViewGroup攔截了該事件。
2访惜、當(dāng)返回為false時(shí),表示當(dāng)前ViewGroup不攔截該事件腻扇。該事件轉(zhuǎn)而傳遞到該ViewGroup的子View债热。
2.2.1 onInterceptTouchEvent返回true
這里先看第一個(gè)分支,當(dāng)返回為true時(shí)幼苛,intercepted的賦值為true窒篱。
if (!canceled && !intercepted) {
//事件傳遞到子View才會(huì)走下面的邏輯
...省略
}
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
...省略
}
intercepted的賦值為true,說(shuō)明事件被當(dāng)前ViewGroup攔截了,mFirstTouchTarget == null條件判斷成立墙杯,所以調(diào)用了dispatchTransformedTouchEvent方法配并。注意,這里的第三個(gè)參數(shù)為null高镐。
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
...省略
// Perform any necessary transformations and dispatch.
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
transformedEvent.recycle();
return handled;
}
上面提到的第三個(gè)參數(shù)為null溉旋,也就是說(shuō)child == null 成立,所以這里執(zhí)行了super. dispatchTouchEvent方法嫉髓。ViewGroup的父類(lèi)是View观腊,所以這里執(zhí)行的是View的dispatchTouchEvent方法。
public boolean dispatchTouchEvent(MotionEvent event) {
...省略
boolean result = false;
...省略
if (onFilterTouchEventForSecurity(event)) {
...省略
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;
}
}
...省略
return result;
}
這里判斷了當(dāng)前View(也就是上面的ViewGroup)的狀態(tài)是否是Enable的算行,并且當(dāng)前View是否通過(guò)setOnTouchListener方法設(shè)置了OnTouchListener接口方法并在onTouch方法中是否返回true梧油。
當(dāng)上面提到的條件都返回true時(shí),result的賦值為true州邢。當(dāng)result的賦值為true時(shí)儡陨,onTouchEvent方法并不會(huì)執(zhí)行。
這里得出一個(gè)結(jié)論:
當(dāng)View設(shè)置了OnTouchListener接口量淌,并在接口方法onTouch中返回true時(shí)骗村,View的onTouchEvent方法不會(huì)執(zhí)行,也就是說(shuō)事件不會(huì)傳遞到View的onTouchEvent方法中类少。
這里討論一般的情況叙身,當(dāng)result為false時(shí),onTouchEvent方法就被執(zhí)行了硫狞。
注意了信轿,事件這里已經(jīng)傳遞到了第一個(gè)頂級(jí)ViewGroup的onTouchEvent方法了。
當(dāng)onTouchEvent返回true時(shí)残吩,表示該事件被當(dāng)前View消耗了财忽,后續(xù)的一系列事件默認(rèn)都會(huì)傳遞給當(dāng)前View。
平時(shí)我們經(jīng)常給View設(shè)置的OnClickListener是在onTouchEvent中觸發(fā)的(源碼中調(diào)用了performClick方法)泣侮。結(jié)合上面得出的結(jié)論即彪,我們可以得到另外一個(gè)結(jié)論:
OnTouch比onTouchEvent先執(zhí)行,onTouchEvent比onClick先執(zhí)行活尊。我們平時(shí)給View設(shè)置的點(diǎn)擊事件是處于最末端執(zhí)行的隶校。
當(dāng)onTouchEvent返回false時(shí),表示當(dāng)前View不處理該事件蛹锰,我們回溯上面的方法深胳,這就相當(dāng)于最開(kāi)始的dispatchTouchEvent方法返回false,繼續(xù)回溯到Activity的dispatchTouchEvent方法铜犬。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
也就是說(shuō)舞终,(getWindow().superDispatchTouchEvent(ev)返回false轻庆,Activity的onTouchEvent方法會(huì)被執(zhí)行。
2.2.2 onInterceptTouchEvent返回false
我們?cè)賮?lái)看第二個(gè)分支敛劝,當(dāng)ViewGroup的onInterceptTouchEvent方法返回false時(shí)余爆,則執(zhí)行如下邏輯。
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
...省略
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
...省略
final View[] children = mChildren;
//遍歷當(dāng)前ViewGroup的每一個(gè)子View夸盟,將該事件分發(fā)下去
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
...省略
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
...省略
}
}
遍歷了當(dāng)前ViewGroup的每一個(gè)子View蛾方,將事件分發(fā)下去。對(duì)于ViewGroup的子View满俗,調(diào)用了dispatchTransformedTouchEvent方法转捕。
還有沒(méi)有印象?前面ViewGroup攔截事件時(shí)也調(diào)用了該方法唆垃,只不過(guò)當(dāng)時(shí)的第三個(gè)參數(shù)為null五芝。
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
...省略
// Perform any necessary transformations and dispatch.
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
transformedEvent.recycle();
return handled;
}
可以看到,當(dāng)child不為null時(shí)辕万,調(diào)用了child的dispatchTouchEvent方法枢步。
如果child是一個(gè)ViewGroup的話,那么事件分發(fā)的邏輯就和上面分析ViewGroup一樣了渐尿。所以醉途,回去對(duì)照上面的事件分發(fā)流程圖。事件分發(fā)到子ViewGroup時(shí)砖茸,邏輯和其父容器的一毛一樣的0妗!凉夯!
當(dāng)dispatchTransformedTouchEvent方法返回true時(shí)货葬,表示事件由該子View消耗了,所以addTouchTarget方法就會(huì)被執(zhí)行劲够。
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
而mFirstTouchTarget就是在這里賦值的震桶。這也驗(yàn)證了上面那個(gè)結(jié)論:
當(dāng)mFirstTouchTarget != null 時(shí),表示事件由當(dāng)前ViewGroup的子View消耗了征绎。
ViewGroup對(duì)于事件分發(fā)的邏輯驗(yàn)證就告一段落了蹲姐。假設(shè)ViewGroup的子View不是一個(gè)容器,而是一個(gè)純粹的View人柿,那么事件分發(fā)邏輯又是怎樣的呢柴墩?
2.3 View對(duì)事件進(jìn)行分發(fā)
接著上面的邏輯,事件繼續(xù)傳遞凫岖,在dispatchTransformedTouchEvent方法中拐邪,如果child是一個(gè)View,那么調(diào)用的就是View的dispatchTouchEvent方法隘截。
public boolean dispatchTouchEvent(MotionEvent event) {
...省略
boolean result = false;
...省略
if (onFilterTouchEventForSecurity(event)) {
...省略
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;
}
}
...省略
return result;
}
我們上面也分析了View的dispatchTouchEvent方法扎阶。這里就只說(shuō)一下結(jié)論:
dispatchTouchEvent方法調(diào)用了onTouchEvent方法,方法的返回值表示事件是否被消耗了婶芭。當(dāng)事件沒(méi)有被消耗东臀,那么mFirstTouchTarget 沒(méi)有被賦值,即mFirstTouchTarget == null犀农。
此時(shí)事件回傳到了子View的父容器惰赋。如果父容器不處理該事件,事件會(huì)繼續(xù)往上傳呵哨,直到事件被消耗赁濒。
當(dāng)事件被消耗了,后面一系列事件都會(huì)傳遞到該View孟害。當(dāng)然在傳遞的過(guò)程中拒炎,ACTION_MOVE和ACTION_UP事件默認(rèn)會(huì)經(jīng)過(guò)父容器的onInterceptTouchEvent方法,父容器可以攔截在該方法中攔截事件挨务。
如果所有View都不消耗該事件击你,那么該事件會(huì)回傳到Activity。Activity如果也不消耗該事件谎柄,那么該事件就消失了丁侄。
三、總結(jié)
本文從源碼的角度分析了View的事件分發(fā)流程朝巫。事件從Activity開(kāi)始傳遞鸿摇,中間經(jīng)過(guò)ViewGroup的分發(fā)、攔截劈猿、消耗或者是View的分發(fā)拙吉、消耗等一系列方法的處理,事件或被ViewGroup糙臼、View消耗庐镐,或繼續(xù)往上傳遞給Activity處理,而這就是一個(gè)完整的事件分發(fā)流程变逃。