一、ViewGroup#dispatchTouchEvent()
ViewGroup#dispatchTouchEvent()
// 处理第一个 down
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 当开始一个新的触摸手势时,丢弃所有以前的状态
cancelAndClearTouchTargets(ev);
// 清除所有接触目标
// (1)mFirstTouchTarget 置为 null
// (2)mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
resetTouchState();
}
// 关键一:检查拦截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN // 当前为 DOWN 事件
|| mFirstTouchTarget != null) { // 接触目标不为空
// 若 mGroupFlags 中设置了 FLAG_DISALLOW_INTERCEPT,此处 disallowIntercept 就为 true,即 intercepted 为 false
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
...
} else {
intercepted = false;
}
} else {
// 没有触摸目标,且当前事件不是 DOWN 事件(表明没有子视图处理事件,当前视图处理事件)
// 此时这个视图组继续拦截触摸
intercepted = true;
}
...
// 当前事件不是 CANCEL 事件,且不拦截
if (!canceled && !intercepted) {
...
// 找到接触目标,即给 mFirstTouchTarget 赋值
if (newTouchTarget == null && childrenCount != 0) {
...
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
...
// 获取指定子视图的触摸目标
newTouchTarget = getTouchTarget(child);
// 找到触摸目标
if (newTouchTarget != null) {
...
// break 跳出 for 循环
break;
}
...
}
...
}
}
// 接触目标为空
if (mFirstTouchTarget == null) {
// 没有接触目标,则把当前 View 作为接触目标,发送事件给接触目标
handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
} else { // 接触目标不为空
...
TouchTarget target = mFirstTouchTarget;
while (target != null) {
...
// 发送事件给接触目标
if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
handled = true;
}
...
}
...
}
...
return handled;
二、分析
关键一:检查拦截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN // 当前为 DOWN 事件
|| mFirstTouchTarget != null) { // 接触目标不为空
// 若 mGroupFlags 中设置了 FLAG_DISALLOW_INTERCEPT,此处 disallowIntercept 就为 true,即 intercepted 为 false
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
...
} else {
intercepted = false;
}
} else {
// 没有触摸目标,且当前事件不是 DOWN 事件(表明没有子视图处理事件,当前视图处理事件)
// 此时这个视图组继续拦截触摸
intercepted = true;
}
其中:
actionMasked == MotionEvent.ACTION_DOWN // 当前为 DOWN 事件
mFirstTouchTarget != null // 接触目标不为空(表明接触目标可以收到 MOVE 和 UP 事件)
所以对于一个事件最终是否被拦截,要看 mGroupFlags
的值是否设置了 FLAG_DISALLOW_INTERCEPT
,没有设置就拦截,有设置就不拦截
requestDisallowInterceptTouchEvent(boolean)
方法可以修改 mGroupFlags
的值,对事件是否需要拦截进行操作
requestDisallowInterceptTouchEvent(boolean)
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
// 已经设置了 FLAG_DISALLOW_INTERCEPT
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// 返回
return;
}
// 根据方法参数 true 或 false 决定是否设置 FLAG_DISALLOW_INTERCEPT
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// 把它传给我们的父视图
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
疑问:既然 Parent 已经做了拦截,事件又是如何传递到 Child#onTouchEvent() 方法中的?
举例,ScrollerView#onTouchEvent() 方法中,对于 DOWN 事件,不拦截;对于 MOVE 事件,滑动距离大于 mTouchSlop 后,才拦截,即不是立马就拦截,所以在 MOVE 事件时,子视图是有机会去请求父视图不拦截事件,可用来以处理滑动冲突