对 requestDisallowInterceptTouchEvent() 方法的理解

一、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 事件时,子视图是有机会去请求父视图不拦截事件,可用来以处理滑动冲突

你可能感兴趣的:(Android,源码分析)