Android View 事件分发

事件传入

事件是从 Activity 通过一层层传递至 View 中

事件分发概念

主要是由 3 个方法来完成

分发重点方法 | 功能 | 返回意义
调度触摸事件：布尔值 | 由上层 View 被触发，传递至目标 View | 返回结果会由 、子 View 的 影响 （true： 被处理）onTouchEvent``dispatchTouchEvent
onInterceptTouchEvent ： boolean | 在当前 View 中，用来判断是否拦截某个事件 | 返回结果表示该事件是否被拦截 （true： 被拦截）
触摸事件：布尔值 | 当前 View 已经拦截，开始处理是建 | 返回结果代表该事件是否被消耗 （true： 被处理）

Copy
public boolean dispatchTouchEvent(MotionEvent e) {
bool isEventConsume = false;

if(onInterceptTouchEvent(e)) {
isEventConsume = onTouchEvent(e);
} else {
isEventConsume = child.dispatchTouchEvent(e);
}

return isEventConsume;
}

可以看出分发顺序 dispatchTouchEvent -> onInterceptTouchEvent，在依照是否消耗来决定之后的走向，dispatchTouchEvent 的递归调用，直到找到消耗事件的 View，View 是一个 树状结构，若该 View 已经拦截点击事件，则会触发 onTouch，onTouch 若没消费该事件，才会传递给 onTouchEvent 方法，onTouchEvent 内才会有 onClick 事件。

Activity 接收事件

Copy
//Activity.java

public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
// 这里的 getWindow 就是 PhoneWindow 类
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}

//PhoneWindow.java

private DecorView mDecor;

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}

//DecorView.java
//DecorView 继承于 FrameLayout，而 FrameLayout 并没有 Override
//dispatchTouchEvent 方法，所以必须往它的父类寻找
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}

// ViewGroup.java
// ViewGroup 继承于 View
// ViewGroup 有重写dispatchTouchEvent，所以不用继续往 View 去
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...
}

事件处理

透过上面分析，我们就知道点击事件是如何传递至 DecorView#ViewGroup 中，这里我们会再分 ViewGroup 点击事件来分析
先了解几个 MotionEvent：

MotionEvent 事件 | 动作 | 其他
ACTION_DOWN | 手指下压 | 又份为拦截、不拦截
ACTION_UP | 手指抬起 | 事件结束
ACTION_MOVE | 在屏幕滑动 | 会被多次触发
ACTION_CANCEL | 事件取消 | 事件被上层拦截时候触发

ViewGroup 处理 - ChildView 拦截 ACTION_DOWN

Down 事件只会触发一次（单点触控，多点触控就不只一次），拦截就是该 ViewGroup 自己处理事件，不会对 ChildView 分发
每个点击事件都是以 Action Down 开始，细节请注意以下的注解，而它主要做的事情有 （这里会先列出主要的处理项目）
清除先前的事件：注意 resetTouchState 方法，它会在 ViewGroup#dispatchTouchEvent 事件是 ACTION_DOWN 时执行

Note

清除先前的事件：注意 resetTouchState 方法，它会在 ViewGroup#dispatchTouchEvent 事件是 ACTION_DOWN 时执行

Copy
// ViewGroup.java

// 从该 ViewGroup 开始，往下串接点击的目标
private TouchTarget mFirstTouchTarget;

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...

boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {

// 取得目前行为
final int action = ev.getAction();
// 与 Mask 进行 and 操作，取得真正的 Action
final int actionMasked = action & MotionEvent.ACTION_MASK;

if (actionMasked == MotionEvent.ACTION_DOWN) {
// 在 Action Down 时才执行 reset

// @ 追踪 cancelAndClearTouchTargets 方法
cancelAndClearTouchTargets(ev);

// @ 追踪 resetTouchState 方法
resetTouchState();
}

}

...

return handled;
}

private void resetTouchState() {
// @ 查看 clearTouchTargets 方法
clearTouchTargets();
resetCancelNextUpFlag(this);

// 清除 FLAG_DISALLOW_INTERCEPT、允许 ViewGroup 拦截
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
mNestedScrollAxes = SCROLL_AXIS_NONE;
}

// 清除第一个点击 View 串列的所有事件
private void clearTouchTargets() {
// TouchTarget 是单向链表
TouchTarget target = mFirstTouchTarget;
if (target != null) {
do {
TouchTarget next = target.next;
// 回收 TouchTarget 方便复用
target.recycle();
target = next;
} while (target != null);

// 将成员 mFirstTouchTarget 至为 null
mFirstTouchTarget = null;
}
}

是否会呼叫 ViewGroup#onInterceptTouchEvent 方法：会有两个条件，再加上一个 FLAG 判断：

目前是 ACTION_DOWN 事件

已经有 ChildView 处理这个点击事件 （如果有子 View 处理事件就会给 mFirstTouchTarget 赋值）

目前 ViewGroup 是否被禁止拦截 （一般 ViewGroup 接收到 时，如果没有禁止拦截的话，就会执行 onInterceptTouchEvent 方法）ACTION_DOWN

Copy
// ViewGroup.java

// 从该 ViewGroup 开始，往下串接点击的目标
private TouchTarget mFirstTouchTarget;

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...

boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {

// 取得目前行为
final int action = ev.getAction();
// 与 Mask 进行 and 操作，取得真正的 Action
final int actionMasked = action & MotionEvent.ACTION_MASK;

// Action down 时,初始化 FLAG_DISALLOW_INTERCEPT

final boolean intercepted;
// 这里有两个判断 决定是否呼叫 ViewGroup 自己的 onInterceptTouchEvent 方法
// 1. 目前是 ACTION_DOWN
// 2. 已经有子 View 处理这个点击事件
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {

// 判断是否禁止拦截
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {

// 呼叫自身的 onInterceptTouchEvent 方法
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
intercepted = true;
}

}

...

return handled;
}

若 ViewGroup 自身没有拦截，就会 递归 ChildView：并一一执行 ViewGroup#dispatchTransformedTouchEvent 分发给每个 ChildView 处理，若 ChildView 消耗事件则返回 true，则跳出循环，否则往下一个 ChildView 询问。

Copy
// ViewGroup

// 从该 ViewGroup 开始，往下串接点击的目标
private TouchTarget mFirstTouchTarget;

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...

boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {

final int action = ev.getAction();
// 与 Mask 进行 and 操作，取得真正的 Action
final int actionMasked = action & MotionEvent.ACTION_MASK;

// Action down 时 初始化 FLAG_DISALLOW_INTERCEPT

final boolean intercepted;
// 判断自身 ViewGroup 是否可以拦截事件

// 目前假设 ViewGroup 不拦截
if (!canceled && !intercepted) {
...

if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
...

if (newTouchTarget == null && childrenCount != 0) {
...
// 重新排列在该 ViewGroup 中的 ChildView 们的顺序
final ArrayList<View> preorderedList =
buildTouchDispatchChildList();
...

for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);

final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);

...

// 是否动画中 canReceivePointerEvents
// 点击是否在 child 元素內 isTransformedTouchPointInView
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}

// 获取点击中的 View
// 新的 View 事件才会返回 非 null
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Give it the new pointer in addition to the ones it is handling.
// Child View 已经接收到点击
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}

// 重点在 dispatchTransformedTouchEvent 方法
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// ChildView 处理事件成功

...

mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();

// addTouchTarget 方法
// 內会赋予 mFirstTouchTarget 值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
// 跳出循环
break;
}

}
}

}
}

}

...

return handled;
}

ViewGroup#dispatchTransformedTouchEvent方法：ViewGroup 会通过该方法把事件传递给 ChildView，若是传入的 child 是 null 就呼叫 ViewGroup 父类的 dispatchTouchEvent，不是 null 则呼叫指定 View 的 dispatchTouchEvent （当前情况就是有传入 View 对象，所以会转跳到指定 View#dispatchTouchEvent 方法)

Copy
// ViewGroup.java

private boolean dispatchTransformedTouchEvent(MotionEvent event,
boolean cancel,
View child,
int desiredPointerIdBits) {

final boolean handled;

...

if (child == null) {
// 如果 Child view 为 null 则回传给 ViewGroup 的 Parent
handled = super.dispatchTouchEvent(transformedEvent);
} else {
...

handled = child.dispatchTouchEvent(transformedEvent);
}

return handled;
}

ViewGroup#addTouchTarget：赋予 成员mFirstTouchTarget
ViewGroup#addTouchTarget：配备 mFirstTouchTarget 成员

Copy
// ViewGroup.java

private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
// 取复用的 TouchTarget 物件

final TouchTarget target = TouchTarget
.obtain(child, pointerIdBits);

// 串接上一个事件的 View
target.next = mFirstTouchTarget;

// 第一个 处理点击事件 的 View
mFirstTouchTarget = target;
return target;
}

在 ChildView 接收并拦截事件后 （通过 方法），会赋予该 ViewGroup#mFirstTouchTarget 值、并跳出循环dispatchTouchEventChildView 处理完事件后，返回到 ViewGroup 继续处理

Copy
// ViewGroup.java

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

...

if (onFilterTouchEventForSecurity(ev)) {

if (mFirstTouchTarget == null) {
...
} else {
...

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;
}

}

}

...

return handled;
}

ViewGroup 处理 - ChildView 不拦截 ACTION_DOWN

ViewGroup ChildView 拦截事件：处理方式相同 （上一小节），这里主要看看不同的部分，该事件由 ViewGroup 自己处理 （dispatchTransformedTouchEvent 方法）

Copy
// ViewGroup.java

// 从 ViewGroup 开始
private TouchTarget mFirstTouchTarget;

@Override
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) {
// 在 Action Down 时才执行 reset

cancelAndClearTouchTargets(ev);

resetTouchState();
}

}

...

return handled;
}

清除先前的事件：注意 resetTouchState 方法，它会在ViewGroup#dispatchTouchEvent，事件是 ACTION_DOWN 时执行，是否会呼叫 ViewGroup#onInterceptTouchEvent 方法：会有两个条件，再加上一个 FLAG 判断，目前是 事件ACTION_DOWN，已经有 ChildView 处理这个点击事件 （如果有子 View 处理事件就会给 赋值）mFirstTouchTarget，目前 ViewGroup 是否被禁止拦截 （一般 ViewGroup 接收到 时，如果没有禁止拦截的话，就会执行，onInterceptTouchEvent 方法）ACTION_DOWN，若 ViewGroup 自身没有拦截，就会递归 ChildView：并一一执行 ViewGroup#dispatchTransformedTouchEvent 分发给每个 ChildView 处理

Copy
// ViewGroup

// 从 ViewGroup 开始
private TouchTarget mFirstTouchTarget;

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...

boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
...

if (!canceled && !intercepted) {
// 目前沒有 ChildView 要处理这个事件
}
}
...
}

ChildView 接收但 全部都不拦截事件，ViewGroup#mFirstTouchTarget 为 null

Copy
// ViewGroup.java

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

...

if (onFilterTouchEventForSecurity(ev)) {

...

// 目前情況，没有 ChildView 处理事件
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
...
}

}

...

return handled;
}

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;

...

if (child == null) {
// 回到 ViewGroup 的 Parent dispatchTouchEvent
handled = super.dispatchTouchEvent(transformedEvent);

} else {
...
}
...
return handled;
}

View 收到事件 - 处理 ACTION_UP

View 首先分发给 onTouch 若是没有处理则分发到 onTouchEvent，顺序如下

最先执行 onTouch 接口，若是已经处理，就 onTouchEvent 就不会接收到事件 onTouch

若 onTouch 没有处理这个事件，就会轮到该 View 的 onTouchEvent 处理事件onTouchEvent

Copy
// View.java

public boolean dispatchTouchEvent(MotionEvent event) {
...

boolean result = false;

...

if (onFilterTouchEventForSecurity(event)) {
...

// 事件分发到 onTouch
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
// 判断该 View 是 enable
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}

// 分析 onTouchEvent
if (!result && onTouchEvent(event)) {
result = true;
}
}

...

return result;
}

在 View#onTouchEvent 方法中，可以看到 onClick、onLongClick 接口的呼叫

Copy
// View.java

// PerformClick 代表该 View 的点击事件
private PerformClick mPerformClick;

public boolean onTouchEvent(MotionEvent event) {

final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();

// 只要设定 CLICKABLE、LONG_CLICKABLE、CONTEXT_CLICKABLE 其中一个，
// 该 View 可点击
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

// 点击事件的代理 TouchDelegate
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}

if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {

case MotionEvent.ACTION_UP:
...

boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;

if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
...

// 长按任务 mHasPerformedLongPress
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// 移除长按 Callback
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {

if (mPerformClick == null) {
mPerformClick = new PerformClick();
}

// 分析 performClickInternal
if (!post(mPerformClick)) {
performClickInternal();
}
}
}

...
}
mIgnoreNextUpEvent = false;
break;
}
}
}

这边可以看到 View 的点击事件是通过 Handler 传送 Click 点击任务 （Runnable），这样就不会造成点击事件堵塞

Copy
// View.java

public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;

if (attachInfo != null) {
// 将 Click 任务放入 Handler
return attachInfo.mHandler.post(action);
}
getRunQueue().post(action);
return true;
}

private boolean performClickInternal() {
notifyAutofillManagerOnClick();

// 分析 performClick
return performClick();
}

public boolean performClick() {
notifyAutofillManagerOnClick();

final boolean result;
final ListenerInfo li = mListenerInfo;

if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
// 执行 OnClick
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}

sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}

ViewGroup - ACTION_DOWN

判断事件是否被 ViewGroup 拦截：
ViewGroup 拦截事件 -> 直接走 3
ViewGroup 不拦截事件 -> 先走 再走 2 3

ViewGroup 不拦截，并有其中一个 ChildView 拦截事件并处理
ViewGroup#newTouchTarget 被赋予值
分发到自身的 View （ 呼叫自己的父类）super.dispatchTouchEvent

Copy
// ViewGroup.java

if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
...

mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}

事件分发、处理：有两种情况
所有 ChildView 不拦截这个事件，所以 mFirstTouchTarget 为 null，相当于是最后一个 View

Copy
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}

有 ChildView 拦截，所以 mFirstTouchTarget 不为 null，并且 while 只循环一次，因为 ChildView 已处理 （分发时处理）

Copy
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
}

ACTION_MOVE - 查看收到的 MOVE

Move 事件仍会先走 DecorView 这个 ViewGroup，在执行 事件时先需要知道几件事情ACTION_MOVE，ViewGroup 不会清理点击的 Flag，TouchTarget 类型的 mFirstTouchTarget 元素 不为空 （因为已经有 ChildView 元素处理），ACTION_MOVE 事件的判断流程图如下，它主要做的事情是 ViewGroup 是否拦截事件 （以下默认不拦截事件），不拦截、Move 不分发事件分发 or 处理，同样先来观察 ViewGroup#dispatchTouchEvent 方法，并从这里开始分析，不会进入 reset 环节

Copy
// ViewGroup.java

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (actionMasked == MotionEvent.ACTION_DOWN) {
...
}
}

判断当前 ViewGroup 是否有禁止拦截 ，如果没有的话就调用自身的 方法 检查是否拦截FLAG_DISALLOW_INTERCEPT onInterceptTouchEvent

Copy
// ViewGroup.java

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

...

// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {

// 判断 ViewGroup 时候禁止拦截
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

// 预设 ViewGroup 不拦截
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
intercepted = true;
}
}

不是 事件，所以不会进入最初的 事件分发环节 ACTION_DOWN

Copy
// ViewGroup.java

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

...

TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;

if (!canceled && !intercepted) {

// 不是 Down 事件
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
...

}
}

...
}

ACTION_MOVE 的重点在事件分发

Copy
// ViewGroup.java

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

...

if (mFirstTouchTarget == null) {
...

} else {

TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;

// alreadyDispatchedToNewTouchTarget 是 false
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;

} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;

// 分析 dispatchTransformedTouchEvent 分發給 ChildView
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
...
}
predecessor = target;
target = next;
}
}
}

接下来通过 dispatchTransformedTouchEvent 方法：分发 事件给 ChildView 的 dispatchTouchEventACTION_MOVE

Copy
// ViewGroup.java

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits)

...

if (child == null) {
...
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}

handled = child.dispatchTouchEvent(transformedEvent);
}
}

ACTION_MOVE - ViewGroup 拦截 MOVE

先了解目前情况：已经有 ChildView 处理 ACTION_DOWN 事件，而我们自订一个 ViewGroup 并复制 ViewGroup#onInterceptTouchEvent 方法，在 ACTION_ VIEW 时返回 true 拦截

Copy
// 自定义 ViewGroup.java

public class MyViewGroup extends View {

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if(ev.getAction() == MotionEvent.ACTION_MOVE) {
// ViewGroup 自己消耗事件
return true;
}
return super.onInterceptTouchEvent(ev);
}

}

ACTION_MOVE 事件是多次发生，第一个 ACTION_MOVE （从 ParentView 进来）的目的是：

取消 ChildView 事件（事件改为 ACTION_CANCEL）

将 ViewGroup#mFirstTouchTarget 置为空，这时 ParentView 是不处理事件的
结果：这时 ChildView 会收到 ACTION_CANCEL 事件（事件被取消，之后都会由 ViewGroup 处理事件）

Copy
/**
* ViewGroup.java
*
* 由于覆写 onInterceptTouchEvent 方法会让 Move 事件为 ture，所以 cancelChild 也为 ture，
* 传入 dispatchTransformedTouchEvent 方法的 cancel 参数为 ture，
*
* 这时事件就会被改为 ACTION_CANCEL，下面的 ChildView 就会接收到 ACTION_CANCEL 事件
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

...

if (mFirstTouchTarget == null) {
...

} else {

TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
// ACTION_MOVE 第一次进入
// intercepted 是 true，所以 cancelChild = true
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;

if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}

// mFirstTouchTarget = next，next为 null
// 所以 mFirstTouchTarget = null
if (cancelChild) {
if (predecessor == null) {
// next 为空，所以 mFirstTouchTarget 为空
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}

...
}

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {

final boolean handled;

final int oldAction = event.getAction();
// cancel 为 true
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {

// 改变事件为 ACTION_CANCEL
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
...
} else {
// 往下分发 ChildView 的事件就为 cancel
// 事件被上层拦截时触发
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}

...

return handled;
}

第二个 ACTION_MOVE（从 ParentView 进来），由于 mFirstTouchTarget 为空，所以 ParentView 不会分发
结果：ParentView 自己处理事件

Copy
//ViewGroup.java

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

// 在第一个 ACTION_MOVE 时 mFirstTouchTarget 被置为空
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {

...
} else {
intercepted = true;
}

// intercepted = true
if (!canceled && !intercepted) {
...
}

if (mFirstTouchTarget == null) {
// ChildView 为空
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {

...
}

...
}

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {

final boolean handled;

...

if (child == null) {
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());
}

handled = child.dispatchTouchEvent(transformedEvent);
}

// Done.
transformedEvent.recycle();
return handled;
}

事件冲突

为何会发生冲突 ？ 因为事件只会有一个，若是响应的组件不是自己要的 View 原件，这时就可以称之为事件冲突
分为两种方法处理

内部拦截法：需要 ChildView#dispatchTouchEvent 处理 & 需要配合改动父容器 （ParentView） 的 onInterceptTouchEvent 拦截

外部拦截法（较常使用）：只需要 ParentView 处理

内部拦截 - ChildView 处理

在 ChildView 中使用 requestDisallowInterceptTouchEvent 方法，控制 FLAG_DISALLOW_INTERCEPT 元素，让 ParentView 不会往下分发事件
内部拦截作法
ChildView 在适当时机通过 方法，要求 ParentView 不要拦截事件requestDisallowInterceptTouchEvent

Copy
public class MyView extends View {

int dispatchX, dispatchY;

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();

switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 要求 ParentView 不拦截
getParent().requestDisallowInterceptTouchEvent(true);
break;

case MotionEvent.ACTION_UP:
break;

case MotionEvent.ACTION_MOVE:
int deltaX = x - dispatchX;
int deltaY = y - dispatchY;

// 事件交由 ViewGroup 处理
if(Math.abs(deltaX) > Math.abs(deltaY)) {
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
}

dispatchX = x;
dispatchY = y;

return super.dispatchTouchEvent(event);
}

}

重写 ParentView 的 方法，设定在 ACTION_DOWN 时不拦截事件，这样事件才能传入 ChildView onInterceptTouchEvent

Copy
public class MyViewGroup extends ViewGroup {

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if(ev.getAction() == MotionEvent.ACTION_DOWN) {
// ViewGroup 不拦截
return false;
}
return super.onInterceptTouchEvent(ev);
}

}

requestDisallowInterceptTouchEvent 细节说明： 为啥 requestDisallowInterceptTouchEvent 无法控制 ACTION_DOWN 事件

Copy
// ViewGroup.java

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;

// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {

cancelAndClearTouchTargets(ev);
// 查看 resetTouchState
resetTouchState();
}

// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) { // mFirstTouchTarget != null

// 控制 FLAG_DISALLOW_INTERCEPT
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
// 如果为 ture 则 ParentView 就不往下分发
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
intercepted = true;
}

//
}
}

@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
}

if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}

// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}

从上面可看出 ViewGroup 时 ACTION_DOWN 使用 resetTouchState () 方法，这里会清理 这个 Flag，导致事件 ACTION_DOWN 一定会被分发FLAG_DISALLOW_INTERCEPT

Copy
// ViewGroup.java
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

...

final boolean intercepted;

if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {

// 清理后 FLAG_DISALLOW_INTERCEPT 为空
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

if (!disallowIntercept) {
// 判断 ViewGroup#onInterceptTouchEvent
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
}
...
}

Note

ViewGroup 中复写 onInterceptTouchEvent，让 ViewGroup 在 ACTION_DOWN 事件时返回 false（不撷取），这样 ACTION_DOWN 就会分发，这样才能往下分发到 ChildView，而其他的动作则会拦截

Copy
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if(ev.getAction() == ACTION_DOWN) {
return false;
}
return true;
}

外部拦截 - ParentView 处理

由 ParentView 处理事件拦截比较简单，只需要覆盖 ParentView#onInterceptTouchEvent 决定哪个时间（条件）拦截事件

Copy
public class MyExternalViewGroup extends ViewGroup {

int interceptX, interceptY;

@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercept = false;
boolean defaultIntercept = super.onInterceptTouchEvent(event);

int x = (int) event.getX();
int y = (int) event.getY();

switch(event.getAction()) {
case MotionEvent.ACTION_MOVE:
int deltaX = x - interceptX;
int deltaY = y - interceptY;

// 水平滑动，拦截
if(Math.abs(deltaX) > Math.abs(deltaY)) {
intercept = true;
} else {
intercept = false;
}
break;

case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_DOWN:
intercept = false;
break;

default:
intercept = defaultIntercept;
break;
}

interceptX = x;
interceptY = y;

return intercept;
}
}