本篇文章將從源碼的角度解析事件分發(fā)機(jī)制的詳細(xì)內(nèi)容聋丝。關(guān)于上篇文章的那些情況迥異的分發(fā)處理過程,是如何在源碼中實(shí)現(xiàn)的工碾?本篇文章將逐一揭曉弱睦。
一、分發(fā)機(jī)制中三個(gè)方法的關(guān)系
上篇文章關(guān)于dispatchTouchEvent()方法渊额,onInterceptTouchEvent()方法和onTouchEvent ()方法的流程進(jìn)行了梳理况木。那么在源碼實(shí)現(xiàn)中,三者之間的關(guān)系具體是什么樣的旬迹?用一段偽代碼來介紹火惊。
/ 點(diǎn)擊事件產(chǎn)生后,會(huì)直接調(diào)用dispatchTouchEvent()方法
public boolean dispatchTouchEvent(MotionEvent ev) {
//代表是否消耗事件
boolean consume = false;
if (onInterceptTouchEvent(ev)) {
//如果onInterceptTouchEvent()返回true則代表當(dāng)前View攔截了點(diǎn)擊事件
//則該點(diǎn)擊事件則會(huì)交給當(dāng)前View進(jìn)行處理
//即調(diào)用onTouchEvent ()方法去處理點(diǎn)擊事件
consume = onTouchEvent (ev) ;
} else {
//如果onInterceptTouchEvent()返回false則代表當(dāng)前View不攔截點(diǎn)擊事件
//則該點(diǎn)擊事件則會(huì)繼續(xù)傳遞給它的子元素
//子元素的dispatchTouchEvent()就會(huì)被調(diào)用奔垦,重復(fù)上述過程
//直到點(diǎn)擊事件被最終處理為止
consume = child.dispatchTouchEvent (ev) ;
}
return consume;
}
上述偽代碼清楚地描述了屹耐,事件分發(fā)從Activity->ViewGroup->View過程中,三大方法之間的調(diào)用關(guān)系椿猎。
二惶岭、Activity中的分發(fā)機(jī)制
上篇文章,我們介紹到犯眠,分發(fā)機(jī)制是從Activity開始的俗他,當(dāng)觸摸屏幕時(shí),Activity先感受到阔逼,并調(diào)用dispatchTouchEvent()方法進(jìn)行分發(fā)兆衅。下面就來具體了解這一方法的內(nèi)容。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//第一次按下操作時(shí),用戶希望能與設(shè)備進(jìn)行交互羡亩,可通過實(shí)現(xiàn)該方法
onUserInteraction();
}
//獲取當(dāng)前Activity的頂層窗口是PhoneWindow,執(zhí)行其superDispatchTouchEvent()方法
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
//當(dāng)沒有任何view處理時(shí)摩疑,交由activity的onTouchEvent處理
return onTouchEvent(ev);
}
可以看到,當(dāng)Activity向下分發(fā)事件畏铆,最終沒有任何控件進(jìn)行處理后雷袋,將會(huì)交給Activity的onTouchEvent()方法處理。
繼續(xù)看superDispatchTouchEvent()
方法
public boolean superDispatchTouchEvent(KeyEvent event) {
return mDecor.superDispatcTouchEvent(event);
}
PhoneWindow的最頂View是DecorView辞居,再交由DecorView處理楷怒。而DecorView的父類的父類是ViewGroup,接著調(diào)用 ViewGroup.dispatchTouchEvent()方法。
所以Activity中的分發(fā)機(jī)制簡述為:若不重寫該方法瓦灶,則調(diào)用根ViewGroup的dispatchTouchEvent()方法鸠删,進(jìn)行分發(fā),如果事件沒有任何控件進(jìn)行處理贼陶,則最后返回給Activity的onTouchEvent()方法進(jìn)行處理刃泡。若重寫該方法,不論返回值是false/true碉怔,都不會(huì)向下進(jìn)行事件分發(fā)烘贴,也就是事件停止分發(fā),已經(jīng)在Activity中消費(fèi)了撮胧。
三桨踪、ViewGroup中的分發(fā)機(jī)制
從Acivity中向下分發(fā),就到達(dá)了ViewGroup中的dispatchTouchEvent()方法芹啥,下面來具體看看這個(gè)方法馒闷。
該方法比較復(fù)雜,篇幅有限叁征,就截取幾個(gè)重要的邏輯片段進(jìn)行介紹纳账,來解析整個(gè)分發(fā)流程。
// 發(fā)生ACTION_DOWN事件或者已經(jīng)發(fā)生過ACTION_DOWN,并且將mFirstTouchTarget賦值捺疼,才進(jìn)入此區(qū)域疏虫,主要功能是攔截器
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {
//disallowIntercept:是否禁用事件攔截的功能(默認(rèn)是false),即不禁用
//可以在子View通過調(diào)用requestDisallowInterceptTouchEvent方法對(duì)這個(gè)值進(jìn)行修改,不讓該View攔截事件
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//默認(rèn)情況下會(huì)進(jìn)入該方法
if (!disallowIntercept) {
//調(diào)用攔截方法
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false;
}
} else {
// 當(dāng)沒有觸摸targets啤呼,且不是down事件時(shí)卧秘,開始持續(xù)攔截觸摸。
intercepted = true;
}
這一段的內(nèi)容主要是為判斷是否攔截官扣。如果當(dāng)前事件的MotionEvent.ACTION_DOWN翅敌,則進(jìn)入判斷,調(diào)用ViewGroup onInterceptTouchEvent()方法的值惕蹄,判斷是否攔截蚯涮。如果mFirstTouchTarget != null治专,即已經(jīng)發(fā)生過MotionEvent.ACTION_DOWN,并且該事件已經(jīng)有ViewGroup的子View進(jìn)行處理了遭顶,那么也進(jìn)入判斷张峰,調(diào)用ViewGroup onInterceptTouchEvent()方法的值,判斷是否攔截棒旗。如果不是以上兩種情況喘批,即已經(jīng)是MOVE或UP事件了,并且之間的事件沒有對(duì)象進(jìn)行處理铣揉,則設(shè)置成true饶深,開始攔截接下來的所有事件。這也就解釋了如果子View的onTouchEvent()方法返回false逛拱,那么接下來的一些列事件都不會(huì)交給他處理敌厘。其實(shí)這并不是onTouchEvent()方法傲嬌,而是onInterceptTouchEvent()方法沒給他機(jī)會(huì)橘券,直接攔截了额湘,不給子View機(jī)會(huì)卿吐。如果VieGroup的onInterceptTouchEvent()第一次執(zhí)行為true旁舰,則mFirstTouchTarget = null,則也會(huì)使得接下來不會(huì)調(diào)用onInterceptTouchEvent()嗡官,直接將攔截設(shè)置為true箭窜。
當(dāng)ViewGroup不攔截事件的時(shí)候,事件會(huì)向下分發(fā)交由它的子View或ViewGroup進(jìn)行處理衍腥。
/* 從最底層的父視圖開始遍歷磺樱,
** 找尋newTouchTarget,即上面的mFirstTouchTarget
** 如果已經(jīng)存在找尋newTouchTarget婆咸,說明正在接收觸摸事件竹捉,則跳出循環(huán)。
*/
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder
? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
// 如果當(dāng)前視圖無法獲取用戶焦點(diǎn)尚骄,則跳過本次循環(huán)
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
//如果view不可見块差,或者觸摸的坐標(biāo)點(diǎn)不在view的范圍內(nèi),則跳過本次循環(huán)
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
// 已經(jīng)開始接收觸摸事件,并退出整個(gè)循環(huán)倔丈。
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
//重置取消或抬起標(biāo)志位
//如果觸摸位置在child的區(qū)域內(nèi)憨闰,則把事件分發(fā)給子View或ViewGroup
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// 獲取TouchDown的時(shí)間點(diǎn)
mLastTouchDownTime = ev.getDownTime();
// 獲取TouchDown的Index
if (preorderedList != null) {
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
//獲取TouchDown的x,y坐標(biāo)
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//添加TouchTarget,則mFirstTouchTarget != null。
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//表示以及分發(fā)給NewTouchTarget
alreadyDispatchedToNewTouchTarget = true;
break;
}
dispatchTransformedTouchEvent()
方法實(shí)際就是調(diào)用子元素的dispatchTouchEvent()
方法需五。
其中dispatchTransformedTouchEvent()
方法的重要邏輯如下:
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
由于其中傳遞的child不為空鹉动,所以就會(huì)調(diào)用子元素的dispatchTouchEvent()。
如果子元素的dispatchTouchEvent()方法返回true宏邮,那么mFirstTouchTarget就會(huì)被賦值泽示,同時(shí)跳出for循環(huán)缸血。
//添加TouchTarget,則mFirstTouchTarget != null。
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//表示以及分發(fā)給NewTouchTarget
alreadyDispatchedToNewTouchTarget = true;
其中在addTouchTarget(child, idBitsToAssign);
內(nèi)部完成mFirstTouchTarget被賦值边琉。
如果mFirstTouchTarget為空属百,將會(huì)讓ViewGroup默認(rèn)攔截所有操作。
如果遍歷所有子View或ViewGroup变姨,都沒有消費(fèi)事件族扰。ViewGroup會(huì)自己處理事件。
所以ViewGroup中的分發(fā)機(jī)制簡述為:若子View或ViewGroup不處理MotionEvent.ACTION_DOWN事件定欧,那么接下來的一些列事件都交由ViewGroup處理渔呵。若ViewGroup的onInterceptTouchEvent()執(zhí)行為true,則接下來的所有事件都默認(rèn)由該ViewGroup執(zhí)行砍鸠。若子View或ViewGroup處理MotionEvent.ACTION_DOWN事件扩氢,則接下來的事件處理交給誰要看onInterceptTouchEvent()的返回值,如果返回true爷辱,則第一個(gè)MOVE事件录豺,會(huì)變成CANCEL事件,繼續(xù)交由原來的子View或ViewGroup處理饭弓,接下來的一些列事件都交由ViewGroup執(zhí)行双饥。如果返回false,則繼續(xù)交給原來的子View或ViewGroup處理弟断。
四咏花、View中的分發(fā)機(jī)制
View中的分發(fā)機(jī)制就比較簡單了。上面ViewGroup中已經(jīng)開始調(diào)用View.dispatchTouchEvent()方法,下面來具體看一下阀趴。
public boolean dispatchTouchEvent(MotionEvent event) {
...
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
//在Down事件之前昏翰,如果存在滾動(dòng)操作則停止。不存在則不進(jìn)行操作
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
ListenerInfo li = mListenerInfo;
//第一個(gè)條件默認(rèn)為true
//第二個(gè)條件mOnTouchListener 不為空
//第三個(gè)條件該條件是判斷當(dāng)前點(diǎn)擊的控件是否enable
//第四個(gè)條件onTouch返回值是true
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true; //滿足上述四個(gè)條件刘急,已經(jīng)消費(fèi)事件棚菊,則返回True
}
//如果OnTouch()返回false,或者沒滿足其他條件叔汁,沒有消費(fèi)Touch事件則調(diào)用OnTouchEvent()
if (!result && onTouchEvent(event)) {
result = true; //onTouchEvent(event)返回true统求,已經(jīng)消費(fèi)事件,則返回True
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// 處理取消或抬起操作
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
針對(duì)上述四個(gè)條件的判斷攻柠,很多View默認(rèn)是(mViewFlags & ENABLED_MASK) == ENABLED
,通過設(shè)置OnTouchListener中的onTouch返回true球订,那么onTouchEvent()方法就不會(huì)調(diào)用,表明OnTouchListener的優(yōu)先級(jí)高于onTouchEvent瑰钮。
//手動(dòng)調(diào)用設(shè)置
button.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return false;
}
});
如果OnTouchListener中的onTouch返回false冒滩,那么會(huì)調(diào)用onTouchEvent()方法。
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
// 當(dāng)View狀態(tài)為DISABLED浪谴,如果可點(diǎn)擊或可長按开睡,則返回True因苹,即消費(fèi)事件
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//當(dāng)View狀態(tài)為ENABLED,如果可點(diǎn)擊或可長按篇恒,則返回True扶檐,即消費(fèi)事件;
//與前面的的結(jié)合,可得出結(jié)論:只要view是可點(diǎn)擊或可長按胁艰,則消費(fèi)該事件.
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
setPressed(true, x, y);
}
if (!mHasPerformedLongPress) {
//這是Tap操作款筑,移除長按回調(diào)方法
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
//調(diào)用View.OnClickListener
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) {
break;
}
//獲取是否處于可滾動(dòng)的視圖內(nèi)
boolean isInScrollingContainer = isInScrollingContainer();
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
//當(dāng)處于可滾動(dòng)視圖內(nèi),則延遲TAP_TIMEOUT腾么,再反饋按壓狀態(tài)奈梳,用來判斷用戶是否想要滾動(dòng)。默認(rèn)延時(shí)為100ms
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
//當(dāng)不再滾動(dòng)視圖內(nèi)解虱,則立刻反饋按壓狀態(tài)
setPressed(true, x, y);
checkForLongClick(0); //檢測是否是長按
}
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
break;
case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);
if (!pointInView(x, y, mTouchSlop)) {
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
removeLongPressCallback();
setPressed(false);
}
}
break;
}
return true;
}
return false;
}
只要View的CLICKABLE和LONG_CLICKABLE有一個(gè)為true攘须,那么它就會(huì)消耗這個(gè)事件。
如果View設(shè)置了OnClickLisenter殴泰,那么performClick方法內(nèi)部就會(huì)調(diào)用onClick方法于宙。
public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}
則表明onTouch()優(yōu)先級(jí)高于onTouchEvent(),并高于onClick()悍汛。
所以View中的分發(fā)機(jī)制簡述為:默認(rèn)情況下捞魁,如果View的CLICKABLE和LONG_CLICKABLE有一個(gè)為true(默認(rèn)LONG_CLICKABLE為false,一般可以點(diǎn)擊的View中CLICKABLE為true员凝。)署驻,則就會(huì)消費(fèi)該事件奋献,如果都為false健霹,則不會(huì)消費(fèi)該事件,dispatchTouchEvent()方法返回false瓶蚂,交由父控件的循環(huán)下一個(gè)子View進(jìn)行同樣操作糖埋。如果重寫onTouchEvent()方法,返回false,dispatchTouchEvent()方法返回false窃这,交由父控件的循環(huán)下一個(gè)子View進(jìn)行同樣操作瞳别。如果重寫onTouchEvent()方法,返回true杭攻,則消費(fèi)該事件祟敛。
以上就是源碼解析事件分發(fā)的內(nèi)容。
參考文章:
Android事件分發(fā)機(jī)制完全解析兆解,帶你從源碼的角度徹底理解(上)
Android事件分發(fā)機(jī)制完全解析馆铁,帶你從源碼的角度徹底理解(下)
Android事件分發(fā)機(jī)制