Android中的事件分發(fā)機(jī)制
在Android開(kāi)發(fā)中二驰,事件的分發(fā)機(jī)制是一塊比較重要的知識(shí)體系,了解并熟悉Android中的事件分發(fā)機(jī)制有助于分析各種點(diǎn)擊滑動(dòng)失效問(wèn)題遵倦,同時(shí)也能更好的去擴(kuò)展控件的事件功能和開(kāi)發(fā)自定義控件署鸡。這篇博客主要就是分析Android中的事件分發(fā)。
首先料祠,來(lái)一張圖說(shuō)明Android中事件是怎樣從Activity怎樣一步一步傳遞到View的:
針對(duì)上圖需要說(shuō)明幾點(diǎn):
- 對(duì)于圖中的dispatchTouchEvent()方法,走return true/false線路時(shí)澎羞,表示是開(kāi)發(fā)者重寫的該方法;走super時(shí)敛苇,表示是開(kāi)發(fā)者沒(méi)有重寫妆绞,或者是重寫時(shí)調(diào)用super.dispatchTouchEvent()方法,表示是調(diào)用父類的處理方式枫攀;
- 圖中的事件分發(fā)流程只是針對(duì)DOWN(按下)事件括饶,MOVE(移動(dòng))和UP(抬起)事件在后面做分析;
針對(duì)上圖做一個(gè)簡(jiǎn)單的分析:
- 從圖中可以看到来涨,事件的分發(fā)過(guò)程可以簡(jiǎn)單的分為三層图焰,Activity層、ViewGroup層和View層蹦掐,在Activity和View層當(dāng)中與事件相關(guān)的方法主要是dispatchTouchEvent()方法和onTouchEvent()方法技羔,而在ViewGroup中還多了一個(gè)onInterceptTouchevent()方法,表示事件攔截的方法卧抗;
- Activity獲取到事件之后藤滥,如果開(kāi)發(fā)者重寫了Activity中的dispatchTouchEvent()方法并返回了true或false,事件就會(huì)被消費(fèi)社裆,否則就會(huì)調(diào)用系統(tǒng)的處理方式拙绊,走super,也就是走到了ViewGroup的dispatchTouchEvent()方法,至于為什么會(huì)從Activity的dispatchTouchEvent()走到ViewGroup中的方法标沪,后面會(huì)有分析榄攀;
- 事件到了ViewGroup組件上,同樣金句,如果開(kāi)發(fā)者重寫了ViewGroup的dispatchTouchEvent()方法并返回了true檩赢,事件就會(huì)被消費(fèi);如果返回的false趴梢,那么事件就會(huì)直接返回給Activity的onTouchEvent()方法漠畜,讓Activity的onTouchEvent()方法來(lái)處理;如果開(kāi)發(fā)者沒(méi)有重寫坞靶,就會(huì)走super憔狞,那么就會(huì)調(diào)用ViewGroup的onInterceptTouchevent()方法(這個(gè)方法表示事件攔截的方法,返回true表示攔截事件彰阴,返回false表示不攔截事件瘾敢,系統(tǒng)默認(rèn)是返回false,不攔截事件)尿这;
- 到了ViewGroup的onInterceptTouchevent()方法簇抵,如果開(kāi)發(fā)者重寫了ViewGroup的onInterceptTouchevent()方法并返回true,那么就會(huì)調(diào)用ViewGroup的onTouchEvent()方法射众,讓ViewGroup的onTouchEvent()方法來(lái)處理事件碟摆;
- 在ViewGroup的onTouchEvent()方法中如果返回true,表示消費(fèi)事件叨橱,返回false典蜕,表示不處理事件,事件將會(huì)傳遞到Activity的onTouchEvent()方法處理罗洗;如果在ViewGroup的onInterceptTouchevent()方法中返回false愉舔,不攔截事件,那么事件就會(huì)傳遞到View中伙菜;
- 事件到了View中轩缤,View就會(huì)調(diào)用自己的dispatchTouchEvent()方法,同樣贩绕,如果開(kāi)發(fā)者重寫了dispatchTouchEvent()方法并返回了true火的,事件就會(huì)被消費(fèi);如果返回的false淑倾,那么事件就會(huì)直接返回給ViewGroup的onTouchEvent()方法卫玖,讓ViewGroup的onTouchEvent()方法來(lái)處理;
- 如果開(kāi)發(fā)者在View中沒(méi)有重寫dispatchTouchEvent()方法踊淳,就會(huì)走super假瞬,系統(tǒng)會(huì)調(diào)用View的onTouchEvent()方法處理事件陕靠,如果在onTouchEvent()方法中返回了true,表示消費(fèi)了事件脱茉,如果返回了false剪芥,那么事件就會(huì)返回到上一層,也就是ViewGroup的onTouchEvent()方法琴许,讓ViewGroup中的onTouchEvent()方法處理事件税肪。
在上面的說(shuō)明中,不僅說(shuō)到了事件從Activity分發(fā)到View的過(guò)程榜田,其實(shí)益兄,事件在ViewGroup和View中的分發(fā)傳遞過(guò)程也已經(jīng)說(shuō)完了,下面這用兩張更加直觀的表示出了ViewGroup和View的事件分發(fā)過(guò)程:
對(duì)于這兩張圖就不做更多的說(shuō)明了箭券,在圖中已經(jīng)表示的非常清楚了净捅。同時(shí)在對(duì)第一張圖片進(jìn)行說(shuō)明的部分也已經(jīng)涉及到了。
下面對(duì)于Android中DOWN事件的分發(fā)過(guò)程做一個(gè)簡(jiǎn)單的總結(jié):
- 對(duì)于ViewGroup和View的disatchTouchEvent()和onTouchEvent()方法辩块,return true表示處理事件蛔六,事件終結(jié);return false表示不處理事件废亭,讓事件回傳到上一層的onTouchEvent()方法中国章,也就是父控件中的onTouchEvent()方法中;
- 對(duì)于Activity的disatchTouchEvent()方法豆村,如果沒(méi)有重寫液兽,就會(huì)通過(guò)調(diào)用ViewGroup的disatchTouchEvent()方法開(kāi)始分發(fā)事件,如果重寫了掌动,那么不管返回true還是false都會(huì)消費(fèi)事件四啰,不在將事件往下分發(fā);
- 對(duì)于dispatchTouchEvent()方法坏匪,如果開(kāi)發(fā)者不重寫,就會(huì)走系統(tǒng)中的默認(rèn)實(shí)現(xiàn)撬统,在ViewGroup中會(huì)調(diào)用ViewGroup的onInterceptTouchEvent()方法适滓,而在View中會(huì)直接把事件分發(fā)給View的onTouchEvent()方法處理;
- 對(duì)于ViewGroup而言恋追,如果ViewGroup要自己處理事件凭迹,需要重寫onInterceptTouchEvent()方法并且返回true,這樣才會(huì)終止事件的傳遞苦囱,并調(diào)用ViewGroup的onTouchEvent()方法處理事件嗅绸,否則調(diào)用系統(tǒng)onInterceptTouchEvent()方法,系統(tǒng)默認(rèn)的返回值是false撕彤,不處理事件將事件傳遞下去鱼鸠;
- 對(duì)于View而言猛拴,在View中是沒(méi)有onInterceptTouchEvent()方法的,因?yàn)樗麤](méi)有孩子控件蚀狰,不需要攔截愉昆,系統(tǒng)在dispatchTouchEvent()方法中默認(rèn)會(huì)把事件分發(fā)給View的onTouchEvent()方法處理;
- 在Android中麻蹋,最開(kāi)始獲取到事件的是Activity跛溉,然后由Activity的dispatchTouchEvent()方法開(kāi)始分發(fā)事件;
- 如果一個(gè)事件由Activity開(kāi)始下發(fā)扮授,但是所有的控件都不處理事件芳室,最終就會(huì)回到Activity的onTouchEvent()方法,如果Activity也不消費(fèi)事件刹勃,那么這個(gè)事件就丟失了堪侯。
上面說(shuō)到了DOWN事件的分發(fā)、傳遞以及處理過(guò)程深夯。
下面就的說(shuō)一下MOVE和UP事件的傳遞過(guò)程:
在Android的事件中抖格,最重要也是最復(fù)雜的一個(gè)事件就是DOWN事件,對(duì)于MOVE和UP事件咕晋,如果一個(gè)控件不能處理DOWN事件雹拄,那么在MOVW和UP的時(shí)候Android系統(tǒng)是不會(huì)將事件分發(fā)給這個(gè)控件的,也就是說(shuō)掌呜,如果一個(gè)控件處理不了DOWN事件滓玖,那么他就接收不到了MOVE和UP事件了,更別說(shuō)處理质蕉;相反势篡,如果一個(gè)控件能處理DOWN事件,那么系統(tǒng)就會(huì)把MOVE和UP事件也交給他處理模暗。下面這張表示了DOWN事件和MOVE禁悠、UP事件的一個(gè)傳遞過(guò)程:
在上圖中,事件由容器控件ViewGroup1傳遞下來(lái)兑宇,如果是DOWN事件碍侦,那么就會(huì)按照上面所說(shuō)的過(guò)程一步一步傳遞,從容器控件ViewGroup1中傳遞到容器控件ViewGroup2在到View中隶糕,然后View也不消費(fèi)事件瓷产,又回傳到容器控件ViewGroup2中,容器控件ViewGroup2的onTouchEvent()方法消費(fèi)了事件枚驻,DOWN事件終結(jié)濒旦;但是如果再次從容器控件ViewGroup1分發(fā)下拉了MONE或者UP事件時(shí),容器控件ViewGroup2就會(huì)直接將事件交給自己的onTouchEvent()方法處理再登,而不會(huì)將事件在次向DWON事件一樣傳遞給View了尔邓。
到這里晾剖,Android中的事件分發(fā)就基本上說(shuō)完了。只是在這里還有2點(diǎn)要簡(jiǎn)單的進(jìn)行說(shuō)明一下:
-
在第一張圖中有2個(gè)方法(dispatchTouchEvent()方法和onTouchEvent()方法)都可以消費(fèi)事件铃拇,
- 如果一個(gè)DOWN事件是在某個(gè)控件的dispatchTouchEvent()方法中消費(fèi)掉的钞瀑,那么MOVE和UP事件傳到這里就會(huì)終止,不會(huì)再往下傳了慷荔;
- 如果一個(gè)DOWN事件是在某個(gè)控件的onTouchEvent()方法中消費(fèi)的雕什,那么MOVE和UP事件就依然會(huì)被傳遞到該控件的onTouchEvent()方法中處理。
-
Android中的控件還有一個(gè)setOnTouchListener()方法
view.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return false; } });
需要注意:
- onTouch()方法優(yōu)越于onTouchEvent()方法先執(zhí)行显晶;
- 如果onTouch()返回true贷岸,那么就不會(huì)執(zhí)行onTouchEvent()方法。
從源碼的角度來(lái)了解一下Android下的事件分發(fā)機(jī)制
同時(shí)也回答上文中留下的一個(gè)問(wèn)題:Activity在做事件分發(fā)時(shí)為什么會(huì)調(diào)用ViewGroup中的dispatchTouchEvent()方法磷雇?
首先看到Activity中的dispatchTouchEvent()方法源碼:
public boolean dispatchTouchEvent(MotionEvent ev) {
// 如果是ACTION_DOWN事件會(huì)走這個(gè)語(yǔ)句偿警,onUserInteraction()這個(gè)方法在系統(tǒng)中是空實(shí)現(xiàn)
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
/**
* 主要看一下這行代碼
* getWindow()表示獲取Window的子類PhoneWindow對(duì)象
* 也就是說(shuō)調(diào)用PhoneWindow中的superDispatchTouchEvent(ev)方法,判斷是否有控件處理事件
*/
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
// 如果沒(méi)有控件能處理事件唯笙,就走這一行代碼螟蒸,調(diào)用Activity的onTouchEvent()方法處理事件
return onTouchEvent(ev);
}
接著進(jìn)入到PhoneWindow中的,查看superDispatchTouchEvent(ev)這個(gè)方法:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
在PhoneWindow類的superDispatchTouchEvent(ev)方法中崩掘,直接調(diào)用了mDecor對(duì)象的superDispatchTouchEvent(ev)方法七嫌,mDecore其實(shí)就是繼承至FrameLayout的DecorView的對(duì)象。在《Activity的組成》這篇博客中貼出了DecorView類的定義源碼苞慢。
接著查看類中的superDispatchTouchEvent(ev)這個(gè)方法:
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
只有一句代碼诵原,super.dispatchTouchEvent(event),調(diào)用父類的dispatchTouchEvent(event)方法挽放,也就是FrameLayout的dispatchTouchEvent(event)方法绍赛,查看FrameLayout類會(huì)發(fā)現(xiàn)FrameLayout并沒(méi)有重寫dispatchTouchEvent(event)方法,那么就是使用的ViewGroup中的dispatchTouchEvent(event)方法辑畦。
到這里也就完全說(shuō)明了Activity在做事件分發(fā)時(shí)調(diào)用的是ViewGroup中的dispatchTouchEvent()方法吗蚌。
查看ViewGroup中的dispatchTouchEvent()方法:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...
boolean handled = false;
// 過(guò)濾觸摸安全策略,如果是false(窗口或控件被遮住了時(shí))纯出,直接跳出觸摸事件
// 如果應(yīng)該分發(fā)事件(調(diào)用onTouch()或onTouchEvdent()方法)蚯妇,則返回True;如果應(yīng)該刪除事件,則返回false
if (onFilterTouchEventForSecurity(ev)) {
...
/**
* 如果是DOWN事件就先將mFirstTouchTarget設(shè)置為null潦刃,
* 然后在resetTouchState()方法中重置狀態(tài)
*/
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 定義變量intercepted標(biāo)記ViewGroup是否攔截Touch事件的傳遞.
final boolean intercepted;
// 事件為ACTION_DOWN或者mFirstTouchTarget不為null(有控件消費(fèi)touch事件)
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
//判斷disallowIntercept(禁止攔截)標(biāo)志位
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//當(dāng)沒(méi)有禁止攔截時(shí)
if (!disallowIntercept) {
// 調(diào)用onInterceptTouchEvent(ev)方法侮措,并將返回值賦給intercepted
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
//當(dāng)禁止攔截時(shí)懈叹,指定intercepted = false乖杠,表示不攔截事件
intercepted = false;
}
} else {
//當(dāng)事件不是ACTION_DOWN并且mFirstTouchTarget為null(沒(méi)有控件消費(fèi)touch事件)時(shí)
//設(shè)置 intercepted = true,表示ViewGroup執(zhí)行Touch事件攔截的操作澄成。
intercepted = true;
}
...
// 事件分發(fā)
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
//不是ACTION_CANCEL事件并且intercepted為false(ViewGroup不攔截事件onInterceptTouchEvent()方法返回false)
if (!canceled && !intercepted) {
//處理ACTION_DOWN事件
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex();
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex):TouchTarget.ALL_POINTER_IDS;
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (childrenCount != 0) {
// 依據(jù)Touch坐標(biāo)尋找孩子控件來(lái)消費(fèi)Touch事件
final View[] children = mChildren;
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
final boolean customOrder = isChildrenDrawingOrderEnabled();
// 遍歷所有孩子控件胧洒,判斷哪個(gè)消費(fèi)Touch事件
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
final View child = children[childIndex];
if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// 找到消費(fèi)Touch事件的孩子控件畏吓,跳出循環(huán),并用newTouchTarget表示孩子控件
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
// 沒(méi)有跳出循環(huán)卫漫,走到這一步菲饼,就會(huì)調(diào)用dispatchTransformedTouchEvent()方法,將事件傳給孩子控件做遞歸處理列赎,第三個(gè)參數(shù)不為null
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
...
}
}
}
/**
* 如果在循環(huán)中沒(méi)有孩子控件消費(fèi)事件并且之前的mFirstTouchTarget不為空
*/
if (newTouchTarget == null && mFirstTouchTarget != null) {
// 將mFirstTouchTarget的賦給newTouchTarget
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
// newTouchTarget指向了最初的TouchTarget
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
/**
* 分發(fā)Touch事件至目標(biāo)控件(target)宏悦,以上過(guò)程主要針對(duì)ACTION_DOWN,
* 如果不是(上一步中判斷intercepted變量)包吝,比如ACTION_MOVE和ACTION_UP饼煞,就是從此處開(kāi)始執(zhí)行
*/
if (mFirstTouchTarget == null) {
/**
* mFirstTouchTarget為null表示Touch事件未被消費(fèi)或Touch事件被攔截了,
* 則調(diào)用ViewGroup的dispatchTransformedTouchEvent()方法诗越,遞歸處理砖瞧,第三個(gè)參數(shù)為null
*/
handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
} else {
/**
* mFirstTouchTarget不為null表示找到了可以消費(fèi)Touch事件的子View
* 并且MOVE或UP事件可以傳遞到該子View
*/
TouchTarget predecessor = null;
// 將找到的可以消費(fèi)事件的mFirstTouchTarget賦給目標(biāo)控件(target)
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
// 如果已經(jīng)分發(fā)到新的控件并且消費(fèi)事件的目標(biāo)控件就是新的控件
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
// 否則調(diào)用dispatchTransformedTouchEvent()方法進(jìn)行遞歸處理,第三個(gè)參數(shù)不為null
if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
handled = true;
}
...
}
predecessor = target;
target = next;
}
}
/**
* 如果是ACTION_UP和ACTION_CANCEL事件嚷狞,還原狀態(tài)
*/
if (canceled|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
...
}
}
...
return handled;
}
我們可以看到在上面的方法中块促,調(diào)用的onInterceptTouchEvent()判斷是否需要攔截事件。
查看ViewGroup中的dispatchTransformedTouchEvent()方法:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
...
if (child == null) {
// 如果孩子控件為空床未,就調(diào)用View的dispatchTouchEvent()方法竭翠,在View的dispatchTouchEvent()方法中會(huì)調(diào)用onTouchEvent()方法
handled = super.dispatchTouchEvent(event);
} else {
// 如果孩子控件不為空,就調(diào)用孩子控件的dispatchTouchEvent()方法
// 在此處孩子控件也還有可能是ViewGroup即硼,所以就是繼續(xù)調(diào)用ViewGroup的dispatchTouchEvent()方法
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
...
transformedEvent.recycle();
return handled;
}
查看ViewGroup中onInterceptTouchEvent()方法:
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
在ViewGroup中逃片,沒(méi)有重寫onTouchEvent()方法,所以調(diào)用的是View中的onTouchEvent()方法只酥。
在View類中褥实,首先看一下View中的dispatchTouchEvent()方法:
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
// 如果是DOWN事件,重置狀態(tài)
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
stopNestedScroll();
}
// 過(guò)濾觸摸安全策略裂允,如果是false(窗口或控件被遮住了時(shí))损离,直接跳出觸摸事件
// 如果應(yīng)該分發(fā)事件(調(diào)用onTouch()或onTouchEvdent()方法),則返回True;如果應(yīng)該刪除事件绝编,則返回false
if (onFilterTouchEventForSecurity(event)) {
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
// 當(dāng)前控件是可用(enabled)的并且View調(diào)用了setOnTouchListener()方法且返回了true僻澎,那么就設(shè)置result為true
result = true;
}
// result為false,表示沒(méi)有調(diào)用setOnTouchListener()方法或該方法返回false十饥,那么就調(diào)用
// View的onTouchEvent()方法
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// 如果是UP事件或CANCEL事件或者是DOWN事件但是該控件不能消費(fèi)事件時(shí)窟勃,重置狀態(tài)
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
最后查看View的onTouchEvent()方法:
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
// 判斷是否有單擊或長(zhǎng)按事件
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// 控件disabled 了,他還能消耗觸摸事件逗堵,只是不相應(yīng)她了
return clickable;
}
// 如果有代理秉氧,調(diào)用代理的方法
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
// 對(duì)點(diǎn)擊事件的具體處理,只要有點(diǎn)擊事件蜒秤,那么onTouchEvent()方法就返回了 true
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
// ...
// mHasPerformedLongPress 表示長(zhǎng)按事件的返回值汁咏,如果長(zhǎng)按事件的的回調(diào)方法返回了true亚斋,那么在同一事件序列中,點(diǎn)擊事件就不會(huì)調(diào)用了(否則會(huì)同時(shí)相應(yīng)長(zhǎng)按事件和點(diǎn)擊事件)
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal(); // 會(huì)調(diào)用 performClick()方法處理單擊事件
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(0, x, y);
break;
}
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// 根據(jù)是否在滾動(dòng)容器中攘滩,使用不同方式調(diào)用長(zhǎng)按事件的回調(diào)
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap(); // 最終調(diào)用長(zhǎng)按事件回調(diào)
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(0, x, y); // 最終調(diào)用長(zhǎng)按事件回調(diào)
}
break;
case MotionEvent.ACTION_CANCEL:
// ...
break;
}
// 只要有點(diǎn)擊事件帅刊,那么onTouchEvent()方法就返回了 true
return true;
}
return false;
}
從上面的代碼來(lái)看,只要View的 CLICLABLE和LONG_CLICKABLE有一個(gè)為true漂问,那么onTouchEvent()方法就會(huì)返回true赖瞒,而且不管該View是否為DISABLE狀態(tài),其他情況返回false蚤假。true表示該控件可以消費(fèi)事件冒黑,false表示該控件不能消費(fèi)事件。現(xiàn)在勤哗,在回過(guò)頭來(lái)看一下第一張圖抡爹,是不是更加的清晰了呢。
最后芒划,對(duì)于View 的 CLICKABLE 和 LONG_CLICKABLE默認(rèn)值冬竟,LONG_CLICKABLE默認(rèn)值為false,但是對(duì)于CLICKABLE就要根據(jù)具體的View來(lái)看了民逼,確切的說(shuō)是可點(diǎn)擊的View的CLICKABLE值為true泵殴,如Button,不可點(diǎn)擊的View的CLICKABLE值為false拼苍,比如TextView笑诅,但是當(dāng)我們調(diào)用了View的 setOnClickListener(@Nullable OnClickListener l) 方法或者 setOnLongClickListener(@Nullable OnLongClickListener l) 方法就會(huì)將對(duì)應(yīng)的值改為true。
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
public void setOnLongClickListener(@Nullable OnLongClickListener l) {
if (!isLongClickable()) {
setLongClickable(true);
}
getListenerInfo().mOnLongClickListener = l;
}