说一说Android事件分发中的requestDisallowInterceptTouchEvent

「这是我参与11月更文挑战的第10天,活动详情查看:2021最后一次更文挑战

前言

我们知道在事件分发过程中是存在一个拦截机制的

onInterceptTouchEvent
复制代码

当它返回true则不向下分发事件,否则向下分发。

但是在这个过程中,还有一个参与者:requestDisallowInterceptTouchEvent,这个函数直接影响事件的拦截。我们今天就来说一说这个这个函数是如何影响事件分发的。

源码分析

我们先看看这个函数的源码

@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);
    }
}
复制代码

可以看到它改变了一个开关FLAG_DISALLOW_INTERCEPT,同时调用其parent的函数。

那么这个开关有什么用?

在ViewGroup的dispatchTouchEvent函数开头有这样一段代码:

final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
        || mFirstTouchTarget != null) {
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    if (!disallowIntercept) {
        intercepted = onInterceptTouchEvent(ev);
        ev.setAction(action); // restore action in case it was changed
    } else {
        intercepted = false;
    }
} else {
    // There are no touch targets and this action is not an initial down
    // so this view group continues to intercept touches.
    intercepted = true;
}
复制代码

先来看判断逻辑,当是down事件或者mFirstTouchTarget不为空,则进入一个代码段;否者拦截设置为true。

我们知道down事件分发过程中,如果有子view消费事件,则赋值给mFirstTouchTarget,后续事件会直接分发给mFirstTouchTarget

这里也可以看出,如果有子View消费了down事件,即mFirstTouchTarget不为空,所以后续事件还会检查拦截。

所以上面就可以理解了,如果down事件中没有子view消费事件,那么后续事件的拦截都为true。所以后续事件不会再遍历子View。

下面再看if代码段

一开始就使用了FLAG_DISALLOW_INTERCEPT开关,即disallowIntercept

disallowIntercept为true,则不拦截;否者判断onInterceptTouchEvent

所以简单来说requestDisallowInterceptTouchEvent设置为true可以跳过onInterceptTouchEvent,不拦截事件。

而且因为requestDisallowInterceptTouchEvent又调用了parent的函数,所以所有层次的父view都不再拦截。

所以requestDisallowInterceptTouchEvent的功能是让这个view及上面的所有父view都放开拦截,即使onInterceptTouchEvent为true。

所以我们一般如下使用

view.getParent().requestDisallowInterceptTouchEvent(true);
复制代码

这样view的所有层次的父view都不会拦截事件了。

扩展思考

下面让我们再深入想想。上面这种的情况是在touch事件发生前设置onInterceptTouchEvent,也是我们一般的用法。但是如果事件发生过程中调用这个函数呢?

比如在view的onTouch的某个事件中使用

getParent().requestDisallowInterceptTouchEvent(true)
复制代码

当事件开始分发时,down事件进入父view的dispatchTouchEvent时,这是子view还未得到事件,所以没有设置requestDisallowInterceptTouchEvent

这时如果父view的onInterceptTouchEvent返回true,即拦截的话,事件则不会分发给子view了,所以requestDisallowInterceptTouchEvent永远不会执行,子view则无法得到事件。

但是如果父view的onInterceptTouchEvent返回false,即不拦截的话,事件就可以分发到子view,requestDisallowInterceptTouchEvent执行,之后的事件都会跳过父view的onInterceptTouchEvent的判断

例如父view的onInterceptTouchEvent代码如下

   public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                return false;
            case MotionEvent.ACTION_MOVE:  
                    return true;
            case MotionEvent.ACTION_UP:
                return true;
            default:
                break;
        }
        return false;   
    }
复制代码

down事件不进行拦截,但是拦截了move和up事件。

如果子view的onTouch的down事件中使用

getParent().requestDisallowInterceptTouchEvent(true)
复制代码

这样down事件分发到了子view,执行了requestDisallowInterceptTouchEvent,同时返回了true。随后move或up事件分发到父view时,因为被设置了FLAG_DISALLOW_INTERCEPT标签,所以就会跳过onInterceptTouchEvent。

所以onInterceptTouchEvent中move和up的返回值设置就无效了,因为根本就不再执行这个函数了。

总结

通过上面的分析可以知道requestDisallowInterceptTouchEvent会让父view放开拦截,并且是向上层层生效的。同时我们也可以通过一些逻辑控制,使requestDisallowInterceptTouchEvent只作用在部分情况下。

Guess you like

Origin juejin.im/post/7031720184691818526