事件處理和沖突
一陆错、View
1. disPatchTouchEvent
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
//先處理onTouch
result = true;
}
在dispatchTouchEvent
方法中先調(diào)用這個(gè)View
的onTouchListener.onTouch
方法灯抛。如果onTouch
返回false
則執(zhí)行下面的流程,否則就返回true
表示此事件已經(jīng)被消費(fèi)音瓷。
//從這里調(diào)用onclick
if (!result && onTouchEvent(event)) {
result = true;
}
在onTouchEvent
中方法判斷如果當(dāng)前事件為ACTION_UP
時(shí)就會(huì)調(diào)用onClick
的回調(diào)
//如果沒能將這個(gè)調(diào)用放到Handler中对嚼,Handler退出了
if (!post(mPerformClick)) {
performClickInternal();
}
public boolean performClick() {
// We still need to call this method to handle the cases where performClick() was called
// externally, instead of through performClickInternal()
notifyAutofillManagerOnClick();
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
在這里就會(huì)調(diào)用到OnClickListener.onClick
方法。
總結(jié):在View.dispatchTouchEvent
方法中會(huì)先執(zhí)行OnTouchLitern.onTouch
方法绳慎,并且onTouch
可以響應(yīng)所有的事件纵竖,如果返回false才會(huì)執(zhí)行OnClickListener.onClick
方法漠烧,onClick
方法只能響應(yīng)ACTION_UP
。如果既要執(zhí)行onTouch
又要執(zhí)行onClick
可以直接調(diào)用performClick
方法靡砌,來(lái)執(zhí)行onClick
方法已脓。
二 . ViewGroup
1. 形成消費(fèi)鏈
final View[] children = mChildren;
//遍歷所有的子View
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//判斷當(dāng)前 的處理鏈中是否包含了這個(gè)View
newTouchTarget = getTouchTarget(child);
//當(dāng)前的點(diǎn)擊鏈中已經(jīng)包含了這個(gè)View
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
//如果包含直接退出循環(huán)
break;
}
這里遍歷 所有的子View
如果當(dāng)前處理鏈中已經(jīng)包含了某個(gè)子View,就直接退出循環(huán)通殃,這個(gè)子View之后的View也就不會(huì)再收到通知度液。如果當(dāng)前的處理鏈中不包含所有的子View,就調(diào)用dispatchTransformedTouchEvent
這個(gè)方法画舌。
// Perform any necessary transformations and dispatch.
if (child == null) {
//父布局的View來(lái)處理
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());
}
//子View來(lái)處理
handled = child.dispatchTouchEvent(transformedEvent);
}
如果傳入的子View為Null
則會(huì)調(diào)用當(dāng)前這個(gè)ViewGroup
的父類(View.dispatchToucEvent
)方法堕担,來(lái)處理onTouch和onClick
事件,如果傳入的不為null這個(gè)方法又會(huì)遞歸的調(diào)用傳入的子View
的dispatchTouchEvent
方法骗炉。如果這個(gè)方法最終返回true
即照宝,有View消費(fèi)了這個(gè)事件,就把這個(gè)View加入到處理鏈中句葵。
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//標(biāo)記是否有新的子View加入到處理的鏈中
alreadyDispatchedToNewTouchTarget = true;
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
在addTouchTraget
方法中會(huì)創(chuàng)建一個(gè)TouchTarget
節(jié)點(diǎn)將,并把剛加入的View
作為鏈頭兢仰,并設(shè)置mFirstTouchTraget
乍丈。
當(dāng)遍歷所有的子View
之并把處理當(dāng)前事件的View加入處理鏈之后就會(huì)跳出循環(huán)(不再通知后面的View)。到這里其實(shí)這個(gè)事件已經(jīng)被下面的View消費(fèi)了把将,接下來(lái)就要標(biāo)記這個(gè)處理的狀態(tài)轻专,用來(lái)告訴這個(gè)ViewGroup
的父View,它是否處消費(fèi)過這個(gè)事件。
// Dispatch to touch targets.
//沒有處理鏈
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
//沒有子View處理這個(gè)事件察蹲,就調(diào)用onTouchEvent方法
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
//有新加入的節(jié)點(diǎn)
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//沒有新加入的節(jié)點(diǎn)就遍歷所有的處理鏈请垛,看他們處不處理
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
這個(gè)方法整體上分為兩個(gè)部分:
-
沒有處理鏈
ViewGroup
自己來(lái)處理這個(gè)事件 -
有處理鏈
這種情況又分為兩種
- 有新加入的節(jié)點(diǎn),直接將handle設(shè)為
true
(因?yàn)樵谇懊孢@個(gè)節(jié)點(diǎn)已經(jīng)處理過了洽议,確認(rèn)要消費(fèi)才加入的) - 沒有新的節(jié)點(diǎn)就遍歷整條消費(fèi)鏈宗收,看是否有節(jié)點(diǎn)要消費(fèi),如果整條鏈上由節(jié)點(diǎn)消費(fèi)了就返回
true
,否則就返回false
亚兄。這種情況下每一個(gè)處理鏈上的節(jié)點(diǎn)處理到收到這個(gè)事件混稽,而不像之前遍歷子View的。
- 有新加入的節(jié)點(diǎn),直接將handle設(shè)為
2. ViewGroup
的二次選擇機(jī)會(huì)
在整個(gè)的處理流程中當(dāng)前的ViewGroup
有兩次機(jī)會(huì)選擇要不要處理這個(gè)事件审胚。
- 開始的時(shí)候判斷是否攔截這個(gè)事件
- 沒有子
View
處理這個(gè)事件
第一種情況 即調(diào)用onInterceptTouchEvent
這個(gè)方法判斷要不要攔截匈勋。
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//down事件由于重置一定為false
//子View通過調(diào)用requestDisallowInterceptTouchEvent,來(lái)告訴父View某個(gè)事件用不用通知子View
//如果為參數(shù)為true就告訴父View不要攔截膳叨,如果參數(shù)為false就讓父View自己決定要不要攔截
//調(diào)用這個(gè)方法最主要的原因就是為了決定要不要走父View的onInterceptTouchEvent方法洽洁。不讓它執(zhí)行就默認(rèn)不攔截
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;
}
}
如果ViewGroup
確定要攔截這個(gè)事件,就不會(huì)再詢問所有的子View菲嘴,而是判斷當(dāng)前處理鏈?zhǔn)欠駷榭斩鲎裕绻麨榭站?code>ViewGroup自己處理碎浇,否則就給處理鏈上的節(jié)點(diǎn)依次處理。
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
//被攔截的時(shí)候回 把這個(gè)事件設(shè)置為Cancle
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
//詢問當(dāng)前View時(shí)
//調(diào)用onTouchEvent
handled = super.dispatchTouchEvent(event);
} else {
//詢問子View時(shí)
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
ViewGroup
在確認(rèn)攔截這個(gè)事件后會(huì)依次通知處理鏈上的每一個(gè)節(jié)點(diǎn)璃俗,并把一個(gè)CANCLE
事件傳遞給這些節(jié)點(diǎn)奴璃。
所以在被攔截之后子節(jié)點(diǎn)會(huì)收到一個(gè)Cancle
類型的事件。
//被攔截之后城豁,會(huì)把鏈表置空苟穆,把表頭置空
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
如果ViewGroup
攔截之后就會(huì)把mFirstTouchTarget
設(shè)置為null
,這樣所有的子View再收到Cancle
事件之后就不能再 收到其他事件的通知了唱星。
在
onInterceptTouchEvent
實(shí)現(xiàn)攔截是ViewGroup
的第一次選擇機(jī)會(huì)
如果所有的子View都不處理這個(gè)Down
事件那么mFirstTouchTarget
就為null
雳旅,就會(huì)調(diào)用這個(gè)View
的disPatchTouchEvent
方法。
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
//沒有子View處理這個(gè)事件间聊,就調(diào)用onTouchEvent方法
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
如果所有的子View都不處理那么這個(gè)
ViewGroup
自己處理就是第二次選擇機(jī)會(huì)
3.子View對(duì)父View的反向制約
從上面的分析中可以看出父View
如果攔截之后子View
就 無(wú)法再消費(fèi)到事件攒盈。
[圖片上傳失敗...(image-247a99-1593159300388)]
造成這種情況的原因是走進(jìn)了if
條件中,如果子View
能夠讓if
條件不滿足就能不調(diào)用父View
的的攔截方法哎榴。通過reqestDisallowInterceptTouchEvent()
方法能夠設(shè)置mGroupFlag
的值型豁,從而調(diào)整if
條件是否滿足。
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
FLAG_DISALLOW_INTERCEPT
是一個(gè)非0的數(shù)尚蝌,如果傳入的參數(shù)為true
那么mGroupFlags&FLAG_DISALLOW_INTERCEPT
就不為0迎变,if
條件就不滿足,就默認(rèn)不攔截飘言;如果參數(shù)為false
時(shí)mGroupFlags&FLAG_DISALLOW_INTERCEPT
就一定 為0衣形,if
條件就滿足就會(huì)執(zhí)行父View
的interceptTouchEvent
方法。
整型值的非運(yùn)算
~7
先化成2進(jìn)制
0000 0111-
取反
1111 1000
-
計(jì)算補(bǔ)碼
1000 0111 + 1 = 1000 1000
-8,首位為1表示負(fù)數(shù)姿鸿。