事件分發(fā)源代碼分析
1. Activity 事件分發(fā)
首先從 Activity 的 dispatchTouchEvent 方法入手
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
可以看出,Activity 其實(shí)是調(diào)用了 Window 的 superDispatchTouchEvent 方法,而 Window 的實(shí)現(xiàn)類是 PhoneWindow,因此我們直接查看 PhoneWindow 的 superDispatchTouchEvent 方法
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
發(fā)現(xiàn)是直接調(diào)用的 DecorView 的 superDispatchTouchEvent 方法,再進(jìn)一步查看
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
原來這兒就調(diào)用了 ViewGroup 的 dispatchTouchEvent 方法,也就是說界面上的事件直接傳遞給了根布局的 dispatchTouchEvent 方法
2. ViewGroup 事件分發(fā)
在講 ViewGroup 的 dispatchTouchEvent 方法之前,我們先看看 ViewGroup 的 dispatchTransformedTouchEvent 方法,dispatchTouchEvent 內(nèi)部多次調(diào)用了 dispatchTransformedTouchEvent 方法。因?yàn)榇a量較多,這里只提取我們關(guān)心的部分
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// 如果是取消操作,則直接分發(fā)取消事件
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
// 如果傳入的 child 不為空,則調(diào)用 child 的 dispatchTouchEvent 方法,否則調(diào)用自身的 dispatchTouchEvent 方法
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
......
// 如果傳入的 child 不為空,則調(diào)用 child 的 dispatchTouchEvent 方法,否則調(diào)用自身的 dispatchTouchEvent 方法
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
......
handled = child.dispatchTouchEvent(transformedEvent);
}
......
return handled;
}
可以看出 dispatchTransformedTouchEvent 方法主要做了兩件事
- 如果傳入的事件是 ACTION_CANCEL,或者 cancel 參數(shù)為 true,則直接分發(fā) ACTION_CANCEL 事件
- 分發(fā)過程中,如果 child 為空,則調(diào)用當(dāng)前 View 的 super.dispatchTouchEvent 方法,這是因?yàn)?ViewGroup 的 dispatchTouchEvent 方法會被重寫,而此時(shí)調(diào)用 super 的方法也就是調(diào)用 View 的 dispatchTouchEvent 方法;如果 child 不為空,則調(diào)用這個(gè)子 View 的 dispatchTouchEvent 方法。
然后我們再來看看 dispatchTouchEvent 方法,同樣代碼量特別多,我們只抽取我們關(guān)心的,即使這樣代碼量依然很多。我們先列出來,不用仔細(xì)看,后面會分塊拆分講解
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
......
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// 1. DOWN 事件進(jìn)行初始化,清空 TouchTargets 和 TouchState
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 2. 檢查是否攔截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// 是否強(qiáng)制不允許攔截,子 View 可以設(shè)置 parent 強(qiáng)制不允許攔截,默認(rèn)為 false
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 {
intercepted = true;
}
......
// 3. 如果沒有被攔截, 先處理 DOWN 事件,主要是賦值 TouchTarget
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
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) {
......
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
......
// 找到 Visible 并且處于點(diǎn)擊范圍的子 View
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
......
// 相當(dāng)于調(diào)用子 View 的 dispatchTouchEvent 方法
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
......
// 賦值 TouchTarget,刷新標(biāo)志位
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
......
}
......
}
......
}
}
// 4. 是自己處理事件還是交由子 View 處理事件
if (mFirstTouchTarget == null) {
// 沒有子 View 消耗事件,則自己消耗,相當(dāng)于調(diào)用 super.dispatchTouchEvent 方法
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
// 如果是 DOWN 事件,則上面已經(jīng)調(diào)用了子 View 的 dispatchTouchEvent 方法,則什么都不用做
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
// 根據(jù) intercepted 決定是否將事件強(qiáng)制改為 CANCEL 事件
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
// 相當(dāng)于調(diào)用子 View 的 dispatchTouchEvent 方法。如果 intercepted=true,此時(shí)會強(qiáng)制將 action 改為 CANCEL;如果 intercepted=false,則
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
// 如果 intercepted=true,則將 mFirstTouchTarget 置為 null
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
......
}
......
return handled;
}
dispatchTouchEvent 方法主要由4個(gè)模塊組成的
- DOWN 事件進(jìn)行初始化,清空 TouchTargets 和 TouchState
- 檢查是否攔截
- 如果沒有被攔截, 先處理 DOWN 事件,主要是賦值 TouchTarget
- 是自己處理事件還是交由子 View 處理事件
A. 第一步:DOWN 事件時(shí)進(jìn)行初始化
// 1. DOWN 事件進(jìn)行初始化,清空 TouchTargets 和 TouchState
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
我們知道,一個(gè)事件序列是由一個(gè) ACTION_DOWN,零個(gè)或多個(gè) ACTION_MOVE 和一個(gè) ACTION_UP 組成的。ACTION_DOWN 是一個(gè)事件序列的開始,ACTION_UP 是一個(gè)事件序列的結(jié)束。在 ACTION_DOWN 時(shí),需要對一些狀態(tài)進(jìn)行初始化和重置。上面的 cancelAndClearTouchTargets 和 resetTouchState 方法,都是初始化一些狀態(tài),最重要的我們關(guān)心的就是內(nèi)部調(diào)用了這個(gè)初始化代碼
private void clearTouchTargets() {
TouchTarget target = mFirstTouchTarget;
if (target != null) {
do {
TouchTarget next = target.next;
target.recycle();
target = next;
} while (target != null);
mFirstTouchTarget = null;
}
}
可以看出來這里是用循環(huán)的方式把單鏈表 mFirstTouchTarget 給清空了,至于 mFirstTouchTarget 是什么這里先不講,只需要知道它保存了一個(gè)指定消耗事件的子 View 便可,后續(xù)的所有事件會直接交給這個(gè) View 消耗。
B. 第二步:檢查是否攔截
// 2. 檢查是否攔截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// 是否強(qiáng)制不允許攔截,子 View 可以設(shè)置 parent 強(qiáng)制不允許攔截,默認(rèn)為 false
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 {
intercepted = true;
}
這里我們可以看出,如果是 ACTION_DOWN 事件,或者 mFirstTouchTarget 不為空的時(shí)候,就會進(jìn)入是否攔截的邏輯判斷。這里有一個(gè) disallowIntercept 的判斷,這個(gè)標(biāo)記是子 View 設(shè)置 ViewGroup 是否允許攔截的情況,這里我們暫不用理解這種情況,默認(rèn) disallowIntercept 為 false,也就是允許攔截。此時(shí)會判斷 onInterceptTouchEvent 方法,而 onInterceptTouchEvent 方法默認(rèn)返回 false。
而 mFirstTouchTarget 是怎么來的呢?在后面會有詳細(xì)的講解,這里先簡單的說明一下:
- 如果 intercepted 為 false,并且在后面有一個(gè)子 View 的 dispatchTouchEvent 方法在 ACTION_DOWN 時(shí)返回了 true,那么就會對 mFirstTouchTarget 進(jìn)行賦值;
- 如果 intercepted 為 true,那么在后面就會對 mFirstTouchTarget 置為 null
我們來走一遍所有可能的路徑:
- 事件序列開始,也就是說,在 ACTION_DOWN 時(shí),會調(diào)用 onInterceptTouchEvent 判斷是否攔截;
- 如果此時(shí) onInterceptTouchEvent 返回 true,intercepted 賦值為 true,則后面 mFirstTouchTarget 不會再被賦值,mFirstTouchTarget 會一直為 null,后續(xù)的 ACTION_MOVE 和 ACTION_UP 事件 onInterceptTouchEvent 方法不會再被調(diào)用,intercepted 會一直為 true
- 如果此時(shí) onInterceptTouchEvent 返回 false,intercepted 賦值為 false,后面如果有子 View 的 dispatchTouchEvent 方法返回了 true,mFirstTouchTarget 會被賦值,后續(xù)的 ACTION_MOVE 和 ACTION_UP 事件每次都會調(diào)用 onInterceptTouchEvent 方法
- 在后續(xù)的 ACTION_MOVE 和 ACTION_UP 事件中,如果 onInterceptTouchEvent 返回了 true,那么在后面會清空 mFirstTouchTarget 的值,那么在下一個(gè)事件直到最后一個(gè)事件,onInterceptTouchEvent 方法又不會被調(diào)用了
總結(jié)來說,onInterceptTouchEvent 一旦某一次返回了 true,那么后面的事件都不會再調(diào)用 onInterceptTouchEvent 進(jìn)行是否攔截的判斷,intercepted 的值會一直為 true
C. 第三步:先處理 DOWN 事件
// 3. 如果沒有被攔截, 先處理 DOWN 事件,主要是賦值 TouchTarget
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
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) {
......
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
......
// 找到 Visible 并且處于點(diǎn)擊范圍的子 View
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
......
// 相當(dāng)于調(diào)用子 View 的 dispatchTouchEvent 方法
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
......
// 賦值 TouchTarget,刷新標(biāo)志位
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
......
}
......
}
......
}
}
這段代碼的前提條件就是 intercepted 為 false,并且是 ACTION_DOWN 事件,此時(shí)會遍歷每一個(gè)滿足條件(處于Visible狀態(tài)并且處于點(diǎn)擊范圍內(nèi))的子 View,調(diào)用了 dispatchTransformedTouchEvent 方法,dispatchTransformedTouchEvent 方法最開始我們就講解過,此時(shí)的 child 參數(shù)不為空,所以就是調(diào)用的 child 的 dispatchTouchEvent 方法。如果 dispatchTransformedTouchEvent 返回為 true,則調(diào)用 addTouchTarget 方法對 mFirstTouchTarget 進(jìn)行賦值,并且將變量 alreadyDispatchedToNewTouchTarget 置為 true,然后緊跟了一個(gè) break 跳出循環(huán),為什么要跳出循環(huán)呢?這是因?yàn)橐粋€(gè)事件只能被一個(gè) View 消耗(dispatchTouchEvent 返回 true 代表消耗)。
我們看看 addTouchTarget 方法
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
就是一個(gè)單鏈表操作,新增一個(gè) TouchTarget 并插入到表頭,然后將表頭賦值給 mFirstTouchTarget。
總結(jié)來說,ACTION_DOWN 時(shí),如果 intercepted 為 true,則不會有任何子 View 調(diào)用 dispatchTouchEvent 方法,并且 mFirstTouchTarget 不會被賦值,會一直為 null;如果 intercepted 為 false,那么在滿足條件的某個(gè)子 View 的 dispatchTouchEvent 方法返回 true 時(shí),mFirstTouchTarget 也會被賦值
D. 第四步:分發(fā)事件
// 4. 是自己處理事件還是交由子 View 處理事件
if (mFirstTouchTarget == null) {
// 沒有子 View 消耗事件,則自己消耗,相當(dāng)于調(diào)用 super.dispatchTouchEvent 方法
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
// 如果是 DOWN 事件,則上面已經(jīng)調(diào)用了子 View 的 dispatchTouchEvent 方法,則什么都不用做
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
// 根據(jù) intercepted 決定是否將事件強(qiáng)制改為 CANCEL 事件
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
// 相當(dāng)于調(diào)用子 View 的 dispatchTouchEvent 方法。如果 intercepted=true,此時(shí)會強(qiáng)制將 action 改為 CANCEL;如果 intercepted=false,則
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
// 如果 intercepted=true,則將 mFirstTouchTarget 置為 null
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
可以看到,只要 mFirstTouchTarget 為 null,那么就會直接調(diào)用 dispatchTransformedTouchEvent 方法,child 參數(shù)為 null,那么就會調(diào)用當(dāng)前 ViewGroup 的 super.dispatchTouchEvent 方法。
如果 mFirstTouchTarget 不為 null,首先判斷 alreadyDispatchedToNewTouchTarget 和 newTouchTarget 變量決定是否直接返回。由上面第3步可知,如果 alreadyDispatchedToNewTouchTarget 為 true,則代表當(dāng)前是 ACTION_DOWN 事件,并且找到了一個(gè)子 View 并且調(diào)用過了 child 的 dispatchTouchEvent 方法,因此這里什么都不用做。
然后再看看 cancelChild 變量,它是由 intercepted 決定的。假如 cancelChild 為 false,說明 intercepted 一定為 false,然后因?yàn)?mFirstTouchTarget 不為空,那么調(diào)用 dispatchTransformedTouchEvent 時(shí) child 不為空,則直接調(diào)用 child 的 dispatchTouchEvent 方法;如果 intercepted 為 true,那么 cancelChild 一定為 true,dispatchTransformedTouchEvent 的 cancel 參數(shù)為 true,那么會直接給 mFirstTouchTarget 對應(yīng)的 child 傳遞一個(gè) ACTION_CANCEL 事件,然后在 mFirstTouchTarget 對應(yīng)的單鏈表中,刪除 mFirstTouchTarget 當(dāng)前對應(yīng)的 TouchTarget
總結(jié)一下,如果 mFirstTouchTarget 為 null,則調(diào)用自身的 super.dispatchTouchEvent 方法;如果 mFirstTouchTarget 不為 null,如果上面已經(jīng)處理過 ACTION_DOWN 事件,則什么都不做;如果 intercepted 為 false,則直接調(diào)用 mFirstTouchTarget 對應(yīng)的 child 的 dispatchTouchEvent方法;如果 intercepted 為 true,則會強(qiáng)制給 mFirstTouchTarget 對應(yīng)的 child 分發(fā)一個(gè) ACTION_CANCEL 事件,然后將 mFirstTouchTarget 置為 null
E. ViewGroup 的 dispatchTouchEvent 事件分發(fā)流程
我們將一個(gè)完整的事件序列的整體流程走一遍
- 當(dāng) ACTION_DOWN 事件時(shí),會將 mFirstTouchTarget 置為 null
- 然后判斷 onInterceptTouchEvent 是否攔截,如果返回 true,則后續(xù)事件都不會再調(diào)用 onInterceptTouchEvent 方法來判斷是否攔截并且默認(rèn)都是 true
- 因?yàn)?intercepted 為 true,并且 mFirstTouchTarget 為 null,因此會直接調(diào)用自身的 super.dispatchTouchEvent 方法。并且后續(xù)事件都會如此執(zhí)行(若下一個(gè)事件能到達(dá))。
- 如果第2步的 onInterceptTouchEvent 返回 false,則會嘗試找到一個(gè)滿足條件的子 View,分發(fā)此次 ACTION_DOWN 事件,并對 mFirstTouchTarget 進(jìn)行賦值
- 如果第4步找不到合適的子 View,mFirstTouchTarget 依然為 null,則會調(diào)用自身的 super.dispatchTouchEvent 方法。并且后續(xù)事件都會如此執(zhí)行(若下一個(gè)事件能到達(dá))。
- 如果第4步找到了合適的子 View,也就意味著分發(fā)了一個(gè) ACTION_DOWN 事件,那么 ACTION_DOWN 就什么都不用做了。
- 當(dāng) ACTION_MOVE 和 ACTION_UP 事件時(shí),如果第4步找到了合適的子 View,mFirstTouchTarget 被賦值,那么會再次判斷 onInterceptTouchEvent 是否攔截。
- 如果第7步判斷為不攔截,則直接分發(fā)此次事件給 mFirstTouchTarget 對應(yīng)的 child
- 如果第7步判斷為攔截,則強(qiáng)制分發(fā) ACTION_CANCEL 事件給 mFirstTouchTarget 對應(yīng)的 child,然后將 mFirstTouchTarget 置為 null
3. View事件分發(fā)
public boolean dispatchTouchEvent(MotionEvent event) {
......
if (onFilterTouchEventForSecurity(event)) {
......
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;
}
看完了 ViewGroup 的 dispatchTouchEvent,再來看 View 的 dispatchTouchEvent 就發(fā)現(xiàn)是不是太簡單了。這里簡單提醒一下:在 ViewGroup 調(diào)用 super.dispatchTouchEvent 其實(shí)就是調(diào)用的 View 的 dispatchTouchEvent 方法。
首先,如果設(shè)置了 mOnTouchListener,則會優(yōu)先處理 mOnTouchListener 的 onTouch 方法,如果 onTouch 方法返回 true,則此方法直接返回而不會再調(diào)用 onTouchEvent 方法了。也就是說 mOnTouchListener 的優(yōu)先級高于 onTouchEvent 方法。如果在 mOnTouchListener 的 onTouch 方法中返回了 true,也許會因?yàn)闆]有執(zhí)行 onTouchEvent 方法而導(dǎo)致點(diǎn)擊等回調(diào)不被調(diào)用。最后會將 mOnTouchListener 或 onTouchEvent 方法的返回值作為此方法的返回值。
然后我們看看 onTouchEvent 方法
public boolean onTouchEvent(MotionEvent event) {
......
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
......
return clickable;
}
......
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
......
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
......
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
......
break;
......
}
return true;
}
return false;
}
首先,只要可以點(diǎn)擊或者長按,clickable 就為 true。不管當(dāng)前控件是不是 DISABLED,都返回 clickable。clickable 根據(jù)不同的控件而值不同,比如 Button 就為 true,TextView 就為 false。設(shè)置對應(yīng)的點(diǎn)擊監(jiān)聽器會默認(rèn)將 clickable 設(shè)為 true。也就是說,只要當(dāng)前控件是可點(diǎn)擊的,那么 onTouchEvent 默認(rèn)返回 true 而消耗這一串事件序列。
在 ACTION_UP 中,會完成點(diǎn)擊事件的判斷,具體是通過 performClickInternal 方法完成的
private boolean performClickInternal() {
// Must notify autofill manager before performing the click actions to avoid scenarios where
// the app has a click listener that changes the state of views the autofill service might
// be interested on.
notifyAutofillManagerOnClick();
return performClick();
}
也就是通過 performClick 方法來完成的
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;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
在這里,顯示觸發(fā) mOnClickListener 的 onClick 方法來完成點(diǎn)擊事件的回調(diào)。
滑動(dòng)沖突解決
根據(jù)以上源代碼分析,我們可以找到一種解決滑動(dòng)沖突的套路:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercept = false;
float x = ev.getX();
float y = ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
intercept = false;
break;
case MotionEvent.ACTION_MOVE:
// 橫向滑動(dòng)則自己處理,豎向滑動(dòng)則子View處理
if (Math.abs(x - lastX) > Math.abs(y - lastY)){
intercept = true;
} else {
intercept = false;
}
break;
case MotionEvent.ACTION_UP:
intercept = false;
break;
}
lastX = x;
lastY = y;
return intercept;
}
- ACTION_DOWN 事件一定要返回 false,否則所有的事件都會被 ViewGroup 攔截,并且不會再調(diào)用 onInterceptTouchEvent 方法。因此 ACTION_DOWN 事件一定會被子 View 所消耗。如果當(dāng)前 ViewGroup 需要在 ACTION_DOWN 處理一些邏輯,則可以在 dispatchTouchEvent 或者 onInterceptTouchEvent 方法處理
- ACTION_MOVE 事件根據(jù)需要來決定是否攔截,一旦攔截,則會立馬給 mFirstTouchTarget 指定的 child 分發(fā)一個(gè) ACTION_CANCEL 事件,后續(xù)的事件序列都直接交由 ViewGroup 消耗,并且不會再調(diào)用 onInterceptTouchEvent 方法
- ACTION_UP 事件的返回值返回 false,如果在之前的事件序列返回過 true,那么 ACTION_UP 返回什么都無所謂,因?yàn)楦静粫粓?zhí)行到;但是如果之前的事件序列沒有返回過 true,那么 ACTION_UP 返回 true,會直接強(qiáng)制替換為 ACTION_CANCEL 分發(fā)給子 View,導(dǎo)致子 View 的點(diǎn)擊事件無法響應(yīng)。