Down --> Move --> Move --> Up
Down --> Pointer_Down --> Move --> Move --> Pointer_Up --> Up
ViewGroup.java
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// 我们已经处于这种状态,假设我们的父View也是如此
return;
}
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// 把它传递给我们的父View
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
//如果事件以无障碍焦点的View为目标,并且此View就是那个无障碍焦点View则开始
//正常事件分发。
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// 处理初始的ACTION_DOWN
if (actionMasked == MotionEvent.ACTION_DOWN) {
//开始新的触摸手势时,丢弃(重置)所有先前的状态。
//框架可能已经丢弃了上一个手势的up或cancel事件
//由于应用程序切换,ANR或其他一些状态更改。
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 检查该ViewGroup是否拦截触摸事件
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {//当触摸事件的Action为ACTION_DOWN,或者mFirstTouchTarget != null(表示子View已经消费了Down事件成为TouchTarget,这个是后续发送给子View的触摸事件(例如Move,Up))
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {// 允许此ViewGroup拦截
// Down事件和发送给TouchTarget的事件都会在这里检测是否拦截
intercepted = onInterceptTouchEvent(ev);
// 恢复Action以防止被更改
ev.setAction(action);
} else {
// 不允许此ViewGroup拦截
intercepted = false;
}
} else {
// 进入这里表明了是Down之后的事件,而TouchTarget为Null,说明子View没有消费之前的Down事件,而是被
// 该ViewGroup拦截消费了。所以后续事件继续被该ViewGroup消费,设置intercepted=true就可以让该ViewGroup的onTouchEvent处理该事件
intercepted = true;
}
//如果该ViewGroup拦截事件,则启动正常事件分发。 同样如果已经存在的一个
//正在处理手势的子View,执行正常的事件分发。
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// 检查 canceled
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// 如果需要,更新pointer down的触摸目标列表。
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
//如果事件的目标是无障碍焦点,如果不处理它,我们会将其提供给
//具有无障碍焦点的View。
//我们清除标志并像往常一样将事件发送给所有子View。
//我们正在查找无障碍焦点宿主以避免保留
//状态,因为这些事件非常罕见。
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // 0总是表示Down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
//清除此指针ID的早期触摸目标,以防变得不同步了。
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
//找到可以接收事件的子View。
//从前到后扫描子View。
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
//如果有一个具有无障碍焦点的View,我们需要它
//首先获取事件并且如果没有处理该事件,我们将执行
//正常分发。
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
//子View已经在其范围内接受了触摸。
//除了正在处理的触摸点之外,还给它新的触摸点(多点触控)。
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// 子View想在它的范围内接收触摸事件
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex指向预先排序列表,找到原始索引
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
// 无障碍焦点不处理这个事件,因此清除标志并且对所有的子View进行的正常事件分发
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
//没有找到子View来接收这个事件。
//将指针分配给最近添加的目标。
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// 分发事件到触摸目标
if (mFirstTouchTarget == null) {
// 没有触摸目标,因此将此ViewGroup作为普通View对待.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} 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 (prtargetedecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
// 如果需要,更新指针up或cancel的触摸目标列表。
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;
}
/**
* 将动作事件(MotionEvent)转换到特定子View的坐标空间,
* 过滤掉不相关的指针(每个手指对应一个指针)ID,并在必要时覆盖其操作。
* 如果child为null,则假定MotionEvent将发送给ViewGroup本身。
*/
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
//取消手势(MotionEvent.ACTION_CANCEL)是一种特殊情况。 我们不需要执行任何转换
//或过滤。 重要的是动作,而不是内容。
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
// 计算要传递的指针数。
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
//如果出于某种原因,我们最终处于一个不一致的状态,看起来
//我们可能产生了一个没有指针的动作事件。当出现这种情况时,我们就丢弃该事件。
// If for some reason we ended up in an inconsistent state where it looks like we
// might produce a motion event with no pointers in it, then drop the event.
if (newPointerIdBits == 0) {
return false;
}
//如果指针的数量相同,并且我们不需要执行任何
//不可逆的转换操作,这时,我们可以重复使用该动作事件进行分发,
//只要我们小心地还原我们所做的任何更改即可。
//否则,我们就需要拷贝一份该动作事件。
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
//child为null,或者child的转换矩阵是单位矩阵。执行完就直接返回了
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);//这里有两种,一种是View的,另一种是ViewGroup的
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
//拷贝动作事件
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// 执行任何必要的转换和分发.
if (child == null) {
//因为子View为null,所以直接分发给自己。super.dispatchTouchEvent对应的是View
//的dispatchTouchEvent方法
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {//child的转换矩阵不是单位矩阵
//通过反向矩阵恢复动作事件
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
// 完毕.
transformedEvent.recycle();
return handled;
}
View.java
/**
*将触摸屏动作事件向下传递到目标View,或当此View是目标时,传递到此View
* @param event 要调度的动作事件。
* @return 如果事件由此View处理,则为True,否则为false。
*/
public boolean dispatchTouchEvent(MotionEvent event) {
// 如果事件应首先由可访问性焦点处理
if (event.isTargetAccessibilityFocus()) {
// 我们没有焦点或没有虚拟后代拥有它,不处理这个事件。
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// 我们有焦点并得到了事件,然后使用正常的事件分发
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
// 如果有用于调试目的的一致性验证对象
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
// 获取动作的int值
final int actionMasked = event.getActionMasked();
// 如果动作是按下
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 新手势的防御性清理。停止正在进行的嵌套滚动
stopNestedScroll();
}
//过滤触摸事件以应用安全策略。True表示应该被分发,False表示应该丢弃
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
// 如果这个View没有调用setEnabled(false),这里应该是相等的
&& (mViewFlags & ENABLED_MASK) == ENABLED
//如果调用了setOnTouchListener(),这里就会回调该Listener
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;//说明OnTouchListener.onTouch方法消费了触摸事件
}
//如果事件没有被onTouch方法消费,则传递给View的onTouchEvent方法
if (!result && onTouchEvent(event)) {
result = true;
}
}
// 如果事件没有被消费,并且有用于调试目的的一致性验证对象
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
//如果这是手势的结束,则在嵌套滚动后清理;
//如果我们已经查验了ACTION_DOWN,但我们不想要其余的
//手势,我们也取消
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
//停止嵌套滚动
stopNestedScroll();
}
return result;
}
/**
* 过滤触摸事件以应用安全策略
*
* @param event 要被过滤的触摸事件
* @return True 如果触摸事件应该被分发, false 如果触摸事件应该被丢弃.
*
* @see #getFilterTouchesWhenObscured
*/
public boolean onFilterTouchEventForSecurity(MotionEvent event) {
//FILTER_TOUCHES_WHEN_OBSCURED 表示View应在其窗口被遮挡时过滤触摸事件。
if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
//FLAG_WINDOW_IS_OBSCURED
// 该标志表示接收到该触摸事件的窗口被其上方的另一个可见窗口部分或全部遮挡。
// 即使事件没有直接通过遮挡区域,该标志也设置为true。 对安全敏感的应用程序可以检查此标志,
// 以识别恶意应用程序可能已覆盖其部分内容的情况,以便误导用户或劫持触摸。
// 适当的回应可能是放弃可疑的触摸或采取额外的预防措施来确认用户的实际意图。
&& (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
// Window 被遮挡, 丢弃这个触摸事件.
return false;
}
return true;
}
Activity.java
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {//View的事件分发从这里开始。 ViewGroup --> ... --> View
return true;
}
return onTouchEvent(ev);//如果没有View消费该Touch事件,则分发到Activity的onTouchEvent方法
}
getAction() 返回 pointer id 和 action (例如: up, down, move)
getActionMasked() 只返回action (例如: up, down, move)
小结:
onInterceptTouchEvent
,当该方法返回true的时候,父View就会拦截后续事件,直到Up事件。拦截的事件都会传递到父ViewGroup的onTouchEvent
。onInterceptTouchEvent
编写判断逻辑,通过返回true来拦截。具体的实践讲解可以参考这篇文章:一文解决Android View滑动冲突