Android事件分发机制详解

一、概要

  对于Android的事件分发机制,刚开始不太了解的人很难搞懂,因为它确实稍微有点复杂,之前我在CSDN上也花了很长时间写过一篇关于Android事件分发机制的文章,现在竟然发现我当时的理解完全是错误的,因此我打算把这片文章重写一下,文章主要分为以下四个部分,1、Android事件分发机制的三个方法,以及它们之间的关系;2、从源码角度理解顶级ViewGroup对事件的分发过程;3、requestDisallowInterceptTouchEvent对事件分发的影响;4、结合实例说明事件分发过程。以下将对这四部分逐一进行讲解。其中很多是参考了《Android开发艺术探索》一书。

二、Android事件分发机制的三个方法

  public boolean dispatchTouchEvent(MotionEvent ev) 用来进行事件分发,如果事件能够传递到当前的View,那么此方法一定能够被调用,它的返回结果受当前View的onTouchEvent下级View的dispatchTouchEvent方法的影响,表示是否要消耗该事件。
  public booleanon onIterceptTouchEvent(MotionEvent ev) 该方法在dispatchTouchEvent方法的内部调用,用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列中,此方法不会再被调用。同一个事件序列的意思是,比如一次滑动事件,它有一个ACTION_DOWN,很多个ACTION_MOVE,以及一个ACTION_UP,那么这些事件就属于一个事件序列。
  public boolean onTouchEvent(MotionEvent ev) 在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一事件序列中,当前View无法再次接收到事件。
三者之间的关系,可以用如下伪代码表示:

public boolean dispatchTouchEvent(MotionEvent ev){

    boolean consume = false;
    if(onInterceptTouchEvent(ev)){
        consume = onTouchEvent(ev);
    }else{
        consume = child.dispatchTouchEvent(ev);
    }
    return consume;
}

  对于一个根ViewGroup来说,当一个点击事件产生以后,首先会传递给它,这时它的dispatchTouchEvent()方法就会被调用,如果这个ViewGroup的onInterceptTouchEvent()方法返回true就表示要拦截当前事件,接着事件就会交给这个ViewGroup处理,即会调用它的onTouchEvent()方法,如果这个ViewGroup的onInterceptTouchEvent()返回false,那么表示不拦截当前事件,这时事件会继续传递给它的子元素,接着子元素的dispatchTouchEvent()方法就会被调用,如此反复直到事件最终被处理。

三、从源码角度理解顶级View对事件的分发过程

  ViewGroup的事件分发过程,主要在ViewGroup的dispatchTouchEvent()方法中实现的,该方法比较长,我们选取比较主要的一段代码来分析。

// Check for interception.
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;
}

  从上面代码我们可以看出,ViewGroup会在如下两种情况下会判断是否要拦截当前事件,事件类型为ACTION_DOWN或者mFirstTouchTarget != null,ACTION_DOWN很好理解,那么mFirstTouchTarger != null呢,从后面的源码我们可以知道,当事件由ViewGroup的子元素成功处理时,mFirstTouchTarget会被赋值并指向子元素,换句话说,当ViewGroup不拦截事件,并将事件交由子元素处理时,mFirstTouchTarget != null,反过来一旦事件由当前View拦截时,mFirstTouchTarget != null就不成立,那么当ACTION_MOVE和ACTION_UP事件到来时,由于actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null这个条件为false,因此,将会导致ViewGroup的onInterceptTouchEvent方法不再调用,并且同一事件序列中的其它方法都会交给它处理。

四、requestDisallowInterceptTouchEvent对事件分发的影响

  从上面的事件分发过程我们可以了解到,当ViewGroup向子View分发事件时,ViewGroup可以对事件进行拦截并处理,但是有时候,子View并不希望父View拦截事件,这时我们就需要使用到requestDisallowInterceptTouchEvent方法来重置mGroupFlags标记位,该方法一般在子View中被调用,我们设置requestDisallowInterceptTouchEvent(true)之后,ViewGroup将无法拦截除了ACTION_DOWN以外的其它事件,为什么可以做到呢,首先我们看下面的requestDisallowInterceptTouchEvent源码:

/**
 * {@inheritDoc}
 */
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);
    }
}

我们结合这段requestDisallowInterceptTouchEvent()方法的源码,以及上面dispatchTouchEvent()方法的部分源码,我们来分析为什么requestDisallowInterceptTouchEvent(true)调用以后,ViewGroup就不会拦截事件了。
requestDisallowInterceptTouchEvent(true)被调用,disallowIntercept 为 true;
1、假设 (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0 为 true;那么将会return;接着我们到dispatchTouchEvent方法中看到final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;这一行,因为(mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0 为 true,所以此时disallowIntercept = true;接着看下面代码:

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

因为disallowIntercept为true,所以,条件!disallowIntercept不成立,所以会执行intercepted = false,dispatchTouchEvent返回false,即不拦截事件。
2、假设(mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0 为 false;即(mGroupFlags & FLAG_DISALLOW_INTERCEPT) = 0, 此时代码会接着会往下执行mGroupFlags |= FLAG_DISALLOW_INTERCEPT;即mGroupFlags = mGroupFlags | FLAG_DISALLOW_INTERCEPT,然后我们再到dispatchTouchEvent方法中看这一句final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
mGroupFlags = mGroupFlags | FLAG_DISALLOW_INTERCEPT
disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0
上面两行代码合起来等价于final boolean disallowIntercept = (mGroupFlags | FLAG_DISALLOW_INTERCEPT & FLAG_DISALLOW_INTERCEPT) != 0,而这段代码又等价于disallowIntercept = (FLAG_DISALLOW_INTERCEPT != 0),即 a | b & b = b,至于为什么,有时间去了解了解java的逻辑运算。因为FLAG_DISALLOW_INTERCEPT != 0,所以disallowIntercept = true,接着会执行intercepted = false;即不拦截事件。

五、结合实例说明事件分发过程

这篇文章已经很长了,实例我将在其他文章中进行讲解

你可能感兴趣的:(Android)