Android事件分发拦截以及处理分析

在Android开发中,经常遇到如下场景,在一个ViewGroup中嵌套有其它ViewGroup或者View,这时点击被嵌套的ViewGroup或者View,这时点击事件到底是怎么被处理的呢?下面就以下面的嵌套为例子来说明
这里写图片描述

从图中可以看出CustomGroupA、CustomGroupB以及CustomView三者之间的嵌套关系,为了说明点击CustomView后,事件最终被处理的情况,我们必须从自定义控件的dispatchTouchEvent、onInterceptTouchEvent以及onTouchEvent这三巨头说起。

1、自定义控件的三巨头方法

如上所述,这里的三巨头就是指自定义控件的dispatchTouchEvent、onInterceptTouchEvent以及onTouchEvent这三个方法了,dispatchTouchEvent是启动方法,在该方法中完成对重载onInterceptTouchEvent以及onTouchEvent这两个重载方法的调用,所以一般而言在自定义控件的时候基本就是用默认的dispatchTouchEvent重载方法,而不作改动。

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    Log.d(TAG,"CustomGroupB: dispatchTouchEvent");
    return super.dispatchTouchEvent(ev);
}

主线程在捕获到点击事件后,会首先调用最外层的ViewGroup的dispatchTouchEvent方法,并通过判断ViewGroup有没有子控件来调用子控件的dispatchTouchEvent方法,这就形成了一个dispatchTouchEvent的调用树。当然是否调用子控件的dispatchTouchEvent方法就要取决于这里的另一巨头方法了,就是onInterceptTouchEvent这个方法,当onInterceptTouchEvent方法返回false时,不拦截事件,于是外层ViewGroup就会调用子控件的dispatchTouchEvent方法,反之就不会。这里点可以从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;
}

当onInterceptTouchEvent返回false时,intercepted也就是false,那么之后的如下判断语句才有可能为true

if (!canceled && !intercepted)

继而获取子控件并执行子控件的dispatchTouchEvent方法,所以我们这里可以分析出来如果上面的CustomGroupA、CustomGroupB的onInterceptTouchEvent方法都返回false的话(需要注意View没有子控件,所以也就没有这里所谓的onInterceptTouchEvent拦截重载方法了),那么这里的方法执行顺序就应该是如下

CustomGroupA: dispatchTouchEvent --> CustomGroupA:onInterceptTouchEvent
CustomGroupB: dispatchTouchEvent --> CustomGroupB: onInterceptTouchEvent

而还一个三巨头即onTouchEvent方法什么时候执行呢,我们上面说了dispatchTouchEvent 形成了一个嵌套树了,那么直到执行到晚最底层控件的dispatchTouchEvent方法后,才会返回去继续执行上层ViewGroup控件未执行完的dispatchTouchEvent方法,所以这里就直接看最底层CustomView的dispatchTouchEvent方法

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    Log.d(TAG,"CustomView: dispatchTouchEvent");
    return super.dispatchTouchEvent(event);
}

这里直接调用到了View父控件的dispatchTouchEvent方法,于是我们来看View的dispatchTouchEvent方法

public boolean dispatchTouchEvent(MotionEvent event) {
    // If the event should be handled by accessibility focus first.
    if (event.isTargetAccessibilityFocus()) {
        // We don't have focus or no virtual descendant has it, do not handle the event.
        if (!isAccessibilityFocusedViewOrHost()) {
            return false;
        }
        // We have focus and got the event, then use normal event dispatch.
        event.setTargetAccessibilityFocus(false);
    }

    boolean result = false;

    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(event, 0);
    }

    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // Defensive cleanup for new gesture
        stopNestedScroll();
    }

    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }

        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }

    if (!result && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }

    // Clean up after nested scrolls if this is the end of a gesture;
    // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
    // of the gesture.
    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }

    return result;
}

这里在判断的过程中,调用三巨头之一的onTouchEvent方法,于是这里我们知道处在最下层的自定义View的onTouch重载方法最新被调用。并且当onTouchEvent方法返回false的时候,View: dispatchTouchEvent可能返回true,不然就返回false了

if (!result && onTouchEvent(event)) {
    result = true;
}

CustomView的dispatchTouchEvent方法调用完成后,继续执行CustomGroupB的dispatchTouchEvent方法,并且根据CustomVIew的dispatchTouchEvent方法返回值来进一步的判断是否需要继续执行CustomGroupB的返回值,当CustomVIew: dispatchTouchEvent返回false时,ViewGroup: dispatchTouchEvent的如下代码条件成立

if (mFirstTouchTarget == null) {
    // No touch targets so treat this as an ordinary view.
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
} 

继续执行dispatchTransformedTouchEvent方法,并在该方法中执行到View的onDispatchTouchEvent方法中去,并调用CustomGroupB: onTouchEvent方法

if (child == null) {
    handled = super.dispatchTouchEvent(event);
} 

当CustomVIew: dispatchTouchEvent返回true时,if (mFirstTouchTarget == null) 条件不成立,不再继续执行ViewGroupB: onTouch方法,并且CustomGroupB: dispatchTouchEvent也返回true那么同理CustomGroupA: onTouchEvent方法也不会执行。这就是三巨头之一的onTouchEvent的作用了,在onTouchEvent重载方法中对事件进行处理,并且通过返回值决定该事件是否传给上层控件继续处理。

这里总结一下三巨头方法的作用

1、dispatchTouchEvent:触发父控件(ViewGroup或View)的dispatchTouchEvent方法,并在该方法中判断是否对事件进行拦截不传给底层控件,或者底层控件在处理事件后,进行判断是否继续讲事件交给上层控件处理;

2、onInterceptTouchEvent:上层控件通过该方法返回值判断,是否拦截事件,返回true拦截事件,false不拦截,在拦截事件后,底层控件的dispatchTouchEvent便没有机会执行,也就更没有机会执行onInterceptTouchEvent以及onTouchEvent方法了;

3、onTouchEvent:对事件进行处理,并根据返回值判断是否传递事件给上层控件处理,返回false继续上传给上层控件处理,返回true便不上传,当不上传时,所有上层控件便都没有机会执行onTouchEvent处理该事件;

通过以上总结我们可以推断出来,如果重写dispatchTouchEvent,并不添加super.dispatchTouchEvent(ev)代码,那么对应控件的onInterceptTouchEvent以及onTouchEvent便无法得到执行,那么它的下层控件也将无法继续执行三巨头方法,上层控件会根据dispatchTouchEvent重写方法来判断是否继续执行onTouchEvent方法,当dispatchTouchEvent返回true时,上层控件不再执行onTouchEvent方法,当dispatchTouchEvent返回false时,上层控件继续执行onTouchEvent方法。

2、结论验证

这一小节,我们来对第一小节的源码分析进行验证,首先重写CustomGroupA、CustomGroupB以及CustomView的三巨头方法返回值分别如下

function CustomGroupA CustomGruopB CustomView
dispatchTouchEvent 默认 默认 默认
onInterceptTouchEvent false fasle 无重载方法
onTouchEvent false false false

按照之前的理论,这里的三个自定义控件都不拦截事件,并且底层控件都将事件处理后交给上层控件处理,那么我们来看看针对不同控件的三巨头方法写的日志的执行情况如下

06-03 14:11:04.546 6512-6512/com.yjing.example D/yjing: CustomGroupA: dispatchTouchEvent
    CustomGroupA: onInterceptTouchEvent
06-03 14:11:04.547 6512-6512/com.yjing.example D/yjing: CustomGroupB: dispatchTouchEvent
    CustomGroupB: onInterceptTouchEvent
    CustomView: dispatchTouchEvent
    CustomView: onTouchEvent
    CustomGroupB: onTouchEvent
    CustomGroupA: onTouchEvent

这就证实了我们上面的分析。

再看另一个情况,如下表所示

function CustomGroupA CustomGruopB CustomView
dispatchTouchEvent 默认 默认 默认
onInterceptTouchEvent false true 无重载方法
onTouchEvent false false false

这里我们在CustomGroupB控件中对时间进行了拦截,那么按照分析,CustomView将没有机会执行三巨头方法了,我们来看日志

06-03 14:07:53.545 6284-6284/com.yjing.example D/yjing: CustomGroupA: dispatchTouchEvent
    CustomGroupA: onInterceptTouchEvent
    CustomGroupB: dispatchTouchEvent
    CustomGroupB: onInterceptTouchEvent
    CustomGroupB: onTouchEvent
06-03 14:07:53.546 6284-6284/com.yjing.example D/yjing: CustomGroupA: onTouchEvent

下面我再看看onTouchEvent方法返回true的情况

function CustomGroupA CustomGruopB CustomView
dispatchTouchEvent 默认 默认 默认
onInterceptTouchEvent false false 无重载方法
onTouchEvent false false true

按照分析,这种情况下CustomGroupA以及CustomGroupB的onTouchEvent方法都不会有机会处理事件了,我们看日志

06-03 14:06:27.271 6046-6046/com.yjing.example D/yjing: CustomGroupA: dispatchTouchEvent
    CustomGroupA: onInterceptTouchEvent
    CustomGroupB: dispatchTouchEvent
    CustomGroupB: onInterceptTouchEvent
    CustomView: dispatchTouchEvent
    CustomView: onTouchEvent

最后,我们看看dispatchTouchEvent方法不用默认方法的情况,我们改动CustomGroupB的dispatchTouchEvent重载方法如下

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    Log.d(TAG,"CustomGroupB: dispatchTouchEvent");
    //return super.dispatchTouchEvent(ev);
    return true;
}

然后三巨头情况如下

function CustomGroupA CustomGruopB CustomView
dispatchTouchEvent 默认 重写 默认
onInterceptTouchEvent false fasle 无重载方法
onTouchEvent false false false

按照上面分析CustomGroupB: dispatchTouchEvent直接返回true那么,CustomGroupA的onTouchEvent方法没有机会执行了,并且由于CustomGroupB: dispatchTouchEvent重载方法没调用super.dispatchTouchEvent(ev);那么CustomGroupB的onInterceptTouchEvent以及onTouchEvent这两巨头方法也不会执行,CustomView的三巨头方法都不会执行了,我们来看日志是不是这样

06-03 14:18:33.624 6888-6888/com.yjing.example D/yjing: CustomGroupA: dispatchTouchEvent
    CustomGroupA: onInterceptTouchEvent
    CustomGroupB: dispatchTouchEvent

有日志可以看出推论正确,所以我们看到dispatchTouchEvent重载方法没调用super.dispatchTouchEvent(ev),那么当前控件以及其下层的控件的三巨头方法都失效了,所以我们一般会使用默认的dispatchTouchEvent方法。

以上就是Android事件的分发(dispatchTouchEvent)、拦截(onInterceptTouchEvent)以及处理(onTouchEvent)分析,但然这里只是结合源码进行了粗略的分析总结,以仅仅来加强理解而已,望童鞋们多多交流,谢谢!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章