1、現(xiàn)象分析
我們分別定義四個(gè)自定義view
// 如果是繼承RelativeLayout則實(shí)現(xiàn)其三個(gè)方法,并輸出log
// dispatchTouchEvent
// onInterceptTouchEvent
// onTouchEvent
public class MyRelaLayout1 extends RelativeLayout{}
public class MyRelaLayout2 extends RelativeLayout {}
// 如果是繼承TextView則實(shí)現(xiàn)其兩個(gè)方法,并輸出log
// dispatchTouchEvent
// onTouchEvent
public class MyTextView1 extends TextView {}
public class MyTextView2 extends TextView {}
寫一個(gè)相互嵌套的布局
<com.djk.test.touch.MyRelaLayout1
android:id="@+id/p_1"
android:layout_width="200dp"
android:layout_height="200dp">
<com.djk.test.touch.MyRelaLayout2
android:id="@+id/p_2"
android:layout_width="200dp"
android:layout_height="200dp">
<com.djk.test.touch.MyTextView1
android:id="@+id/c_1"
android:layout_width="150dp"
android:layout_height="150dp"
android:background="#000000" />
<com.djk.test.touch.MyTextView2
android:id="@+id/c_2"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#ff0000" />
</com.djk.test.touch.MyRelaLayout2>
</com.djk.test.touch.MyRelaLayout1>
實(shí)現(xiàn)效果如下圖,MyTextView1為黑色背景,MyTextView2為紅色背景,并覆蓋在MyTextView1之上

下面我們來(lái)進(jìn)行操作來(lái)分析touch事件的傳遞與攔截
現(xiàn)象一:就現(xiàn)在的默認(rèn)情況下,我們點(diǎn)擊紅色區(qū)域
-> MyRelaLayout1.dispatchTouchEvent.Down -> MyRelaLayout1.onInterceptTouchEvent.Down
-> MyRelaLayout2.dispatchTouchEvent.Down -> MyRelaLayout2.onInterceptTouchEvent.Down
-> MyTextView2.dispatchTouchEvent.Down -> MyTextView2.onTouchEvent.Down
-> MyTextView1.dispatchTouchEvent.Down -> MyTextView1.onTouchEvent.Down
-> MyRelaLayout2.onTouchEvent.Down
-> MyRelaLayout1.onTouchEvent.Down
我們得到touch事件從MyRelaLayout1-> MyRelaLayout2-> MyTextView2-> MyTextView1
可以分析得出touch傳遞是從最外層的MyRelaLayout1傳到第二層MyRelaLayout2
在RelativeLayout布局中,MyTextView2 和MyTextView1屬于平等關(guān)系,那么最上層子view先拿到touch事件,然后再傳遞到下面的子view
現(xiàn)象二:我們給MyTextView2設(shè)置onTouchEvent事件return true
-> MyRelaLayout1.dispatchTouchEvent.Down -> MyRelaLayout1.onInterceptTouchEvent.Down
-> MyRelaLayout2.dispatchTouchEvent.Down -> MyRelaLayout2.onInterceptTouchEvent.Down
-> MyTextView2.dispatchTouchEvent.Down -> MyTextView2.onTouchEvent.Down
// ... Move
-> MyRelaLayout1.dispatchTouchEvent.Up -> MyRelaLayout1.onInterceptTouchEvent.Up
-> MyRelaLayout2.dispatchTouchEvent.Up -> MyRelaLayout2.onInterceptTouchEvent.Up
-> MyTextView2.dispatchTouchEvent.Up -> MyTextView2.onTouchEvent.Up
可以看出我們成功的把MyTextView1的所有事件都截?cái)嗔?/p>
現(xiàn)象三:我們給MyRelaLayout2設(shè)置onInterceptTouchEvent事件return true
-> MyRelaLayout1.dispatchTouchEvent.Down -> MyRelaLayout1.onInterceptTouchEvent.Down
-> MyRelaLayout2.dispatchTouchEvent.Down -> MyRelaLayout2.onInterceptTouchEvent.Down -> MyRelaLayout2.onTouchEvent.Down
-> MyRelaLayout1.dispatchTouchEvent.Up -> MyRelaLayout1.onInterceptTouchEvent.Up
-> MyRelaLayout2.dispatchTouchEvent.Up -> MyRelaLayout2.onTouchEvent.Up
可以看出,成功將后面所有的事件都截?cái)嗔耍⒄{(diào)用了自己的onTouchEvent方法
2、源碼分析(android-25)
public boolean dispatchTouchEvent(MotionEvent ev) {
// ...
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 清除touch targets 其核心代碼就是 mFirstTouchTarget = null;
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 是否要攔截
final boolean intercepted;
// 如果是Down事件,或 mFirstTouchTarget != null;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// 子類請(qǐng)求不要攔截事件
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
// 子類沒(méi)有請(qǐng)求不要攔截事件,走我們正常的流程
if (!disallowIntercept) {
// 調(diào)用自己的onInterceptTouchEvent方法來(lái)判斷是否要攔截touch事件 默認(rèn)情況下返回 fase
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
intercepted = true;
}
// ...
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
// 沒(méi)有取消 & 沒(méi)有攔截事件 正常情況下if能夠執(zhí)行
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) {
// ...
// 獲取其子view的集合
final View[] children = mChildren;
// 反序for循環(huán),獲取子view(這里就是RelativeLayout中多層覆蓋的情況下,首先拿到最上層的子view)
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// ...
newTouchTarget = getTouchTarget(child);
// ...
// 詳見(jiàn)下面的第二個(gè)代碼塊,如果dispatchTouchEvent方法返回true 則能進(jìn)入這個(gè)方法
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// ...
// addTouchTarget() 方法核心代碼 mFirstTouchTarget = target; 這里將 mFirstTouchTarget 賦值
// 然后將mFirstTouchTarget 的值再賦值給 newTouchTarget
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
if (preorderedList != null) preorderedList.clear();
}
}
}
// 這里如果mFirstTouchTarget = null,則去調(diào)用代碼塊2的方法,傳入的child為null,這個(gè)時(shí)候就會(huì)調(diào)用自己的onTouchEvent方法
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
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;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
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;
}
}
// Update list of touch targets for pointer up or cancel, if needed.
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
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);
// 如果child 為null,則去調(diào)用父類的dispatchTouchEvent,ViewGroup的父類也是View,
// 所以相當(dāng)于調(diào)用了View的dispatchTouchEvent,進(jìn)而會(huì)調(diào)用自己的onTouchEvent方法
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {// 如果child 不為null,則去調(diào)用子類的dispatchTouchEvent,子類的touch事件從這里開(kāi)始
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
return handled;
}
3、源碼變通(android源碼中,所有的touch事件都會(huì)調(diào)用dispatchTouchEvent方法,其實(shí)我們可以將它拆成Down事件和其他事件來(lái)分析)
第一步,當(dāng)前是Down事件:
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
mFirstTouchTarget = null;
boolean intercepted = false;
// 調(diào)用自己的onInterceptTouchEvent方法來(lái)判斷是否要攔截touch事件 默認(rèn)情況下返回 false
intercepted = onInterceptTouchEvent(ev);
// 這里如果是不攔截touch事件
if (!intercepted) {
// 獲取其子view的集合
final View[] children = mChildren;
// 反序for循環(huán),獲取子view(這里就是RelativeLayout中多層覆蓋的情況下,首先拿到最上層的子view)
for (int i = childrenCount - 1; i >= 0; i--) {
final View child = getChildView(i);
// 調(diào)用子類的dispatchTouchEvent,如果返回true則將 mFirstTouchTarget 賦值
handled = child.dispatchTouchEvent(event);
if (handled) {
// 這里將 mFirstTouchTarget 賦值
mFirstTouchTarget = target;
// 跳出循環(huán),此后的view就收不到touch事件了
break;
}
}
}
if (mFirstTouchTarget == null) {
// 如果是mFirstTouchTarget == null,則說(shuō)明沒(méi)有一個(gè)子類返回true,
// 則直接調(diào)用View的dispatchTouchEvent,進(jìn)而會(huì)調(diào)用自己的onTouchEvent方法
handled = super.dispatchTouchEvent(event);
}
return handled;
}
第二步,當(dāng)前是 Move or Up 事件:
public boolean dispatchTouchEvent(MotionEvent ev) {
// ...
boolean handled = false;
// 是否要攔截
final boolean intercepted;
// 這里的 mFirstTouchTarget 是由Down事件記錄的
if (mFirstTouchTarget != null) {
// 調(diào)用自己的onInterceptTouchEvent方法來(lái)判斷是否要攔截touch事件 默認(rèn)情況下返回 fase
intercepted = onInterceptTouchEvent(ev);
} else {// 如果Down事件記錄的 mFirstTouchTarget == null; 則攔截判斷直接置為 true
intercepted = true;
}
// 這里如果是不攔截touch事件
if (!intercepted) {
// 獲取其子view的集合
final View[] children = mChildren;
// 反序for循環(huán),獲取子view(這里就是RelativeLayout中多層覆蓋的情況下,首先拿到最上層的子view)
for (int i = childrenCount - 1; i >= 0; i--) {
final View child = getChildView(i);
// 調(diào)用子類的dispatchTouchEvent,如果返回true則將 mFirstTouchTarget 賦值
handled = child.dispatchTouchEvent(event);
if (handled) {
// 這里將 mFirstTouchTarget 賦值
mFirstTouchTarget = target;
// 跳出循環(huán),后面的view就收不到touch事件
break;
}
}
}
if (mFirstTouchTarget == null) {
// 如果是mFirstTouchTarget == null,則說(shuō)明沒(méi)有一個(gè)子類返回true,
// 則直接調(diào)用View的dispatchTouchEvent,進(jìn)而會(huì)調(diào)用自己的onTouchEvent方法
handled = super.dispatchTouchEvent(event);
}
return handled;
}

4、下面來(lái)總結(jié)一下吧
①、ACTION_DOWN事件為一個(gè)事件序列的開(kāi)始,中間有若干個(gè)ACTION_MOVE,最后以ACTION_UP結(jié)束。
②、一個(gè)clickable或者longClickable的View會(huì)永遠(yuǎn)消費(fèi)Touch事件,不管他是enabled還是disabled的
③、Touch事件是從最頂層的View一直分發(fā)到手指touch的最里層的View,如果最里層View消費(fèi)了ACTION_DOWN事件(設(shè)置onTouchListener,并且onTouch()返回true 或者onTouchEvent()方法返回true)才會(huì)觸發(fā)ACTION_MOVE,ACTION_UP的發(fā)生,如果某個(gè)ViewGroup攔截了Touch事件,則Touch事件交給ViewGroup處理
④、如果某一個(gè)View攔截了事件,那么同一個(gè)事件序列的其他所有事件都會(huì)交由這個(gè)View處理,此時(shí)不再調(diào)用View(ViewGroup)的onIntercept()方法去詢問(wèn)是否要攔截了。