事件分发机制学习笔记

Android菜鸟一枚,做项目的时候经常碰到滑动冲突,于是痛下狠心学了一下事件分发机制,并且通过翻看源码,稍有心得。

事件分发的基础

说到事件的分发机制,不得不提到三个方法:
dispatchTouchEvent(MotionEvent ev);
onInterceptTouchEvent(MotionEvent ev);
onTouchEvent(MotionEvent event);
在ViewGroup中三个方法全部存在,而在View中,不存在onInterceptTouchEvent(MotionEvent ev);
当点击事件发生的时候,事件首先会传给activity,此时activity的dispatchTouchEvent(MotionEvent ev)方法执行。然后事件传给window,通过window将事件传给顶级view。接下来就是我们的重点了。
我们都知道view是放在ViewGroup中的,而子ViewGroup又是放在父ViewGroup中,这样一层一层就形成了View树,当一个事件发生的时候,应该交给谁来处理呢?

有必要了解一下上面三个方法了:

《Android开发艺术探索》上有一段伪代码把这三个方法的关系表现的淋漓尽致。

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

dispatchTouchEvent(MotionEvent ev): 如果事件能传到这里的话,该方法一定会被调用,用于对事件进行分发。
onInterceptTouchEvent(MotionEvent ev): 如果事件能传到该view,当action为ACTION_DOWN的时候,该方法一定执行,用来询问要不要拦截,如果确定拦截的话,事件传到该view后便不会再往下传,并且之后的一系列事件便不会走该方法;如果该View处理事件的话(ouTouchEvent返回值为true),那么之后的一系列事件都会交给该view处理;
onTouchEvent(MotionEvent event):该方法是大家在自定义控件中最经常重写的方法,用于对事件进行处理。如果返回true的话,那么down事件及其之后的一系列事件都交给所在的view执行;如果该view不处理的话,事件再上传给父View的onTouchEvent方法,而且我测试后发现这种情况下,之后的一系列方法竟然不会再传到该View了,而是传给那个处理事件的View。

假设:ViewGroupFather—>ViewGroupChild—>View
那么首先毫无疑问,事件会传给ViewGroupFather,dispatchTouchEvent(MotionEvent ev)首先执行。事件传递的流程如下所示:
事件被消耗后,便不再传给其它View了,之后的一系列事件都会交给该View处理
值得注意的是:如果一个View设置了OnTouchListener,OnTouchListener.onTouch()将会执行。伪代码如下:

if (mOnTouchListener!=null){
    if (!mOnTouchListener.onTouch(view, event)){
         onTouchEvent(event);
    }
 }else {
     onTouchEvent(event);
 }

如果还设置有点击监听OnClickListener,将在onTouchEvent(event)的case ACTION_UP:case MotionEvent.ACTION_UP:{}中执行performClick()方法。从下面的代码中,我们可以看到点击事件的执行了。

 public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }
        ...
        return result;
    }

重要的都在dispatchTouchEvent(MotionEvent ev)中,应该好好看看该方法的代码。

VewGroup的dispatchTouchEvent方法

从该方法的开始看,有以下代码

if (actionMasked == MotionEvent.ACTION_DOWN) {
      cancelAndClearTouchTargets(ev);
      resetTouchState();
}

上面代码中我们来看resetTouchState()方法,该方法中调用了clearTouchTargets(),而在这个方法中有这么一句代码mFirstTouchTarget = null,对mFirstTouchTarget 清空。
接下来继续看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;
            }

在上面代码中,如果进入if语句块,需要满足两个条件actionMasked == MotionEvent.ACTION_DOWN和mFirstTouchTarget != null,那么当事件为down时候,肯定会走这个语句块。当事件为move和up的时候就不好说了,这个时候需要看mFirstTouchTarget != null成不成立,上文知道当action为down的时候为null,那么它什么时候赋值呢?由下文可以知道,当ViewGroup的子元素处理事件的时候,mFirstTouchTarget会被赋值。也就是说如果当前ViewGroup拦截该事件,那么onInterceptTouchEvent(ev)不再被调用,之后的一系列事件都会交给该ViewGroup处理。
但是,从代码中我们可以看出来,当前ViewGroup如果想要走onInterceptTouchEvent(ev),还需要disallowIntercept为false,也就是FLAG_DISALLOW_INTERCEPT为false。这个标记位平常很少用到,但是requestDisallowInterceptTouchEvent()方法我们经常在ViewGroup中用到,这个方法便是设置mFirstTouchTarget的。

如果当前ViewGroup不拦截事件,将会走dispatchTouchEvent的以下代码:

                        for (int i = childrenCount - 1; i >= 0; i--) {
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }
                            newTouchTarget = getTouchTarget(child);
                            if (dispatchTransformedTouchEvent(ev, false, child,           idBitsToAssign)) {
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
                        }

上面代码,我只是挑出的一些重点,并不是完整的代码。
首先对ViewGroup中的子view进行遍历,如果不符合条件的就直接continue了,不再往下走了。怎么才是符合条件呢?canViewReceivePointerEvents是判断子元素是否在播放动画,isTransformedTouchPointInView是判断点击的事件座标是否落在了子元素的区域内。
我们点进去看一下dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)方法:

           if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;

当然此时child不为空,将会走孩子的dispatchTouchEvent方法,并且在addTouchTarget(child, idBitsToAssign)中对mFirstTouchTarget进行赋值,跳出循环。
如果所有子元素都没有处理,那么接着走dispatchTouchEvent以下代码:

if (mFirstTouchTarget == null) {
       handled = dispatchTransformedTouchEvent(ev, canceled, null,
       TouchTarget.ALL_POINTER_IDS);
 } 

因为子元素没有处理,所以mFirstTouchTarget == null,进入dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS)。这个方法的代码已经贴在上面,此时的child为null。会走super.dispatchTouchEvent(event)。这里就转到了View的dispatchTouchEvent,而在View的dispatchTouchEvent中,不再分发事件,也没有调用onInterceptTouchEvent(MotionEvent ev)方法的情况,而是走的OnTouchEvent。该部分在上文也已经提到,看看代码就明白了:

            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;
            }
发布了32 篇原创文章 · 获赞 7 · 访问量 4920
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章