1. 事件分發(fā)最重要的三個方法
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
}
- dispatchTouchEvent(MotionEvent ev)
用來進(jìn)行事件分發(fā),如果事件能到達(dá)當(dāng)前View楣黍,那么此方法一定會被調(diào)用,而且是先調(diào)用钉凌。返回值表示是否消耗當(dāng)前事件遏考。 - onInterceptTouchEvent(MotionEvent ev)
在dispatchTouchEvent方法內(nèi)部調(diào)用,用來判斷是否攔截某個事件鸟款,如果當(dāng)前View攔截了某個事件膏燃,那么在同一個事件序列當(dāng)中,此方法不會再被調(diào)用何什。返回值表示是否攔截當(dāng)前事件蹄梢。 - onTouchEvent(MotionEvent event)
在dispatchTouchEvent方法中調(diào)用,用來處理點擊事件富俄,返回結(jié)果表示是否消耗當(dāng)前事件。
三者關(guān)系用偽代碼說明:
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;
if (onInterceptTouchEvent(ev)) {
consume = onTouchEvent(ev);
} else {
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
2. 一些結(jié)論
- 同一個事件序列是指從手指接觸屏幕的那一刻開始而咆,到手指離開屏幕的那一刻結(jié)束霍比,在這個過程中所產(chǎn)生的一系列事件。這個事件以down事件開始暴备,中間含有數(shù)量不定的move事件悠瞬,最終以up事件結(jié)束。
- 正常情況下,一個事件序列只能被一個View攔截且消耗浅妆。因為一旦一個元素攔截了某事件(down事件)望迎,那么同一事件序列內(nèi)的所有事件都會交給它處理。但是可以通過其他特殊手段凌外,比如一個View將本該自己處理的事件通過onTouchEvent強行傳遞給其他View處理辩尊。
- 某個View一旦決定攔截(onInterceptTouchEvent),那么一個事件序列都只能由它來處理康辑,并且它的oninterceptTouchEvent不會再被調(diào)用摄欲。
- 某個View一旦開始處理(onTouchEvent)事件,如果不消耗ACTION_DOWN事件疮薇,那么同一事件序列的其他事件都不會再交給它來處理胸墙,并且事件將重新交由它的父元素處理,即父元素的onTouchEvent方法會被調(diào)用按咒。
- 如果View不消耗ACTION_DOWN以外的其他事件迟隅,那么這個點擊事件會消失,此時父元素的onTouchEvent并不會被調(diào)用励七,并且當(dāng)前View可以持續(xù)收到后續(xù)事件智袭,最終這些消失的點擊事件會傳遞給Activity處理。
- ViewGroup默認(rèn)不攔截任何事件呀伙,源碼中ViewGroup的onInterceptTouchEvent方法默認(rèn)返回false补履。
- View沒有onInterceptTouchEvent方法,一旦有點擊事件傳遞給它剿另,那么它的onTouchEvent方法就會被調(diào)用箫锤。
- View的onTouchEvent默認(rèn)都會消耗事件(返回true),除非它是不可點擊的(clickable和longClickable同時為false)雨女。View的longClickable屬性默認(rèn)都為false谚攒,clickable屬性要分情況,比如button的clickable屬性默認(rèn)為true氛堕,而TextView的clickable屬性默認(rèn)為false馏臭。
- View的enable屬性不影響onTouchEvent的默認(rèn)返回值。哪怕一個View是disable狀態(tài)讼稚,只要它的clickable或longClickable有一個為true括儒,那么它的onTouchEvent返回的就是true。
- onClick會發(fā)生的前提是當(dāng)前View是可點擊的锐想,并且它收到了down和up事件帮寻。
- 事件傳遞過程是由外向內(nèi)的,即事件總是先傳遞給父元素赠摇,然后再由父元素分發(fā)給子元素固逗,子元素可以通過requestDisallowInterceptTouchEvent方法可以在子元素中干預(yù)父元素的事件分發(fā)過程浅蚪,但是ACTION_DOWN事件除外。
3. Activity對點擊事件的分發(fā)過程
點擊事件用MotionEvent來表示烫罩,當(dāng)一個點擊操作發(fā)生時惜傲,事件最先傳遞給當(dāng)前Activity,又Activity的dispatchTouchEvent來進(jìn)行分發(fā)贝攒,具體工作由Activity內(nèi)部的Windwo來完成盗誊。Window會將事件傳遞給decor view,decor view一般就是當(dāng)前界面的底層容器(即setContentView所設(shè)置的View的父容器)饿这,通過Activity.getWindow().getDecorView()獲得浊伙。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
window是個抽象類,window的唯一實現(xiàn)是PhoneWindow长捧,看PhoneWindow的分發(fā)事件方法
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
DecorView extends FrameLayout implements RootViewSurfaceTaker{}
我們在activity中可以獲取DecorView
getWindow().getDecorView()
我們通過setContentView方法設(shè)置的Veiw是DecorView的子View∠桑現(xiàn)在事件已經(jīng)到ViewGroup了,繼續(xù)看ViewGroup的分發(fā)串结。
4. 頂級View對點擊事件的分發(fā)過程
事件到達(dá)頂級View后哑子,肯定會進(jìn)入dispatchTouchEvent方法中,該方法中首先判斷是否攔截肌割,攔截則當(dāng)前Veiw自己處理卧蜓,處理方式要先看是否有onTouchListener,有則執(zhí)行onTouchListener并根據(jù)其返回值看是否執(zhí)行OnTouchEvent把敞。不攔截則找到當(dāng)前點擊位置的子View繼續(xù)分發(fā)弥奸。View中dispatchTouchEvent方法的源碼:
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
繼續(xù)看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;
}
ViewGroup在兩個條件下會判斷是否攔截當(dāng)前事件,ACTION_DOWN事件時或者mFirstTouchTarget不為空時奋早。mFirstTouchTarget會在事件由ViewGroup子元素成功處理時盛霎,被賦予子元素的值。
也就是事件被子元素處理了耽装,mFirstTouchTarget有值愤炸,沒被子元素處理,也就是被當(dāng)前ViewGroup攔截了掉奄,則mFirstTouchTarget就沒有值规个,就不滿足條件了。
假如down已經(jīng)被當(dāng)前viewGroup攔截姓建,當(dāng)move和up事件到來時诞仓,mFirstTouchTarget是空,所以會直接執(zhí)行intercepted=true速兔,也就是直接攔截move和up事件都交給當(dāng)前View處理狂芋。否則intercepted的值是onInterceptTouchEvent方法的返回值。
判斷了上面兩個條件憨栽,下面還有一個判斷:
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;
}
這里有一個標(biāo)志位FLAG_DISALLOW_INTERCEPT,在子view中可以通過下面方法設(shè)置:
getParent().requestDisallowInterceptTouchEvent(true);
一旦設(shè)置后,ViewGroup將無法攔截除了ACTION_DOWN以外的其他點擊事件屑柔。
為什么除了ACTION_DOWN呢屡萤?因為在執(zhí)行上面的代碼前,還有一些代碼
// 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();
}
如果是ACTION_DOWN掸宛,會重置標(biāo)記位死陆。由此我們知道兩點:
- 第一點,onInterceptTouchEvent不是每次事件都會被調(diào)用唧瘾,如果我們想提前處理所有的點擊事件措译,要選擇dispatchTouchEvent方法,只有這個方法能確保每次都調(diào)用饰序。
- 另外一點领虹,F(xiàn)LAG_DISALLOW_INTERCEPT標(biāo)記位能幫我們解決滑動沖突。
攔截或者不攔截由intercepted決定求豫,上面的條件判斷最后都會給intercepted賦值塌衰。然后看攔截和不攔截的代碼如下:
if (!canceled && !intercepted) {
// 不攔截
}
if (mFirstTouchTarget == null) {
// 攔截注意這里的dispatchTransformedTouchEvent方法.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}
然后繼續(xù)看ViewGroup在不攔截時的詳細(xì)代碼。
for (int i = childrenCount - 1; i >= 0; i--) {
...
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
...
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
...
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
...
}
遍歷所有元素蝠嘉,判斷子元素是否能夠接受到點擊事件最疆。下面兩個方法就是判斷標(biāo)準(zhǔn),第一個表示是否可見以及是否有動畫蚤告。第二個表示點擊事件是否落在子元素的區(qū)域內(nèi)努酸。
private static boolean canViewReceivePointerEvents(View child) {
return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null;
}
protected boolean isTransformedTouchPointInView(float x, float y, View child,
PointF outLocalPoint) {
final float[] point = getTempPoint();
point[0] = x;
point[1] = y;
transformPointToViewLocal(point, child);
final boolean isInView = child.pointInView(point[0], point[1]);
if (isInView && outLocalPoint != null) {
outLocalPoint.set(point[0], point[1]);
}
return isInView;
}
如果有滿足的元素則執(zhí)行dispatchTransformedTouchEvent方法
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
...
}
因為傳入的child不為null,所以調(diào)用子View的dispatchTouchEvent繼續(xù)分發(fā)杜恰。
如果子元素的分發(fā)返回了true获诈,則上面的代碼繼續(xù)執(zhí)行addTouchTarget方法
private TouchTarget addTouchTarget(View child, int pointerIdBits) {
TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
發(fā)現(xiàn)如果子View消耗掉了點擊事件,則給mFirstTouchTarget賦值箫章。它有了值烙荷,后續(xù)的move和up還會判斷是否要攔截。它沒有值檬寂,則直接由當(dāng)前View處理终抽。
如果遍歷所有的子View都沒有消耗事件。則調(diào)用
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}
注意第三個參數(shù)child為null桶至,則依據(jù)上面的源碼知道會調(diào)用super的dispatchTouchEvent方法昼伴。super是View,下面看View的dispatchTouchEvent方法
5. View的事件處理
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;
}
}
首先是onTouchListener的判斷镣屹,然后執(zhí)行的onTouchEvent方法圃郊,在onTouchEvent的ACTION_UP時,會判斷并調(diào)用click方法女蜈。
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
這里注意ViewGroup和View的dispatchTouchEvent方法是不同的持舆,ViewGroup中的分發(fā)有攔截判斷色瘩;View中的分發(fā)只有onTouchListener的判斷,接著就調(diào)用了onTouchEvent方法逸寓。而ViewGroup是沒有重寫onTouchEvent方法的居兆,在事件攔截后,ViewGroup會調(diào)super的dispatchTouchEvent竹伸,也就是View的dispatchTouchEvent泥栖,在里面調(diào)onTouchEvent方法,當(dāng)然我們可以自己重寫onTouchEvent方法勋篓。
讀完這一章吧享,覺得作者自己很清楚,但寫出來還是覺得混亂譬嚣,連個流程圖都沒有钢颂。全是文字堆積,讓人看的昏昏欲睡孤荣。這里推薦兩篇郭霖的文章甸陌,相比之下還是比較清楚的,如果兩個結(jié)合來學(xué)習(xí)大有益處盐股。
Android事件分發(fā)機制完全解析钱豁,帶你從源碼的角度徹底理解(上)
Android事件分發(fā)機制完全解析,帶你從源碼的角度徹底理解(下)
附加一篇簡書上的另一片文章疯汁,他總結(jié)的比我好:
Android View 事件分發(fā)機制源碼詳解(ViewGroup篇)