Android事件分发机制源码分析及总结

事件的分发在 View 中和 ViewGroup 中有所不同,分两部分进行分析

1. View 中的事件分发

View 的事件分发都从 dispatchTouchEvent() 开始,其逻辑相对简单,关键代码只有以下几行:

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;
}

ListenerInfo 保存了各种touch,click,longclick 等listener,result = true 代表该view消费本次事件,可以看到只有两种情况:
1. 该 View 是 enabled 且 OnTouchListener.onTouch() 返回 ture
2. 若1不成立,再判断 View 的 onTouchEvent() 是否返回 ture

OnTouchListener.onTouch() 取决于我们自己的实现,而 onTouchEvent() 是有默认实现的,代码比较长,下面提炼出关键部分,其余省略

    public boolean onTouchEvent(MotionEvent event) {

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            ...
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return (((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
        }

        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

        if (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
            (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                ...
                // Only perform take click actions if we were in the pressed state
                if (!focusTaken) {
                    // Use a Runnable and post this rather than calling
                    // performClick directly. This lets other visual state
                    // of the view update before click actions start.
                    if (mPerformClick == null) {
                        mPerformClick = new PerformClick();
                    }
                    if (!post(mPerformClick)) {
                        performClick();
                    }
                }
                break;

                ...
            }

            return true;
        }

        return false;
    }

可以看到return 语句有四处,按照以下的顺序进行判断:

  1. 如果 View 被设为 disable 的,但只要这个 View 是 CLICKABLE 、LONG_CLICKABLE 或 CONTEXT_CLICKABLE,那么依然消耗这个事件,直接返回true。可见,View 是否 enable,与是否消耗点击事件没有必然联系,只与界面显示以及是否触发 Listener 有关
  2. 如果设置了 TouchDelegate,那么返回值完全由 TouchDelegate.onTouchEvent() 决定;
  3. 如果这个 View 是 CLICKABLE 、 LONG_CLICKABLE 或 CONTEXT_CLICKABLE 的,那么这里有两个关键点:
  • MotionEvent.ACTION_UP 时会执行 performClick(),也即调用 OnClickListener.onClick()
  • 一定会返回 true
  1. 如果以上条件都没有满足,返回 false 即不消耗事件

View 的事件分发还是比较简单的,总结一下:

  • 如果 View 是 CLICKABLE 、 LONG_CLICKABLE 或 CONTEXT_CLICKABLE,不管它是否是 enable,那么 dispatchTouchEvent 一定会返回 ture,即一定会消耗事件
  • 如果一个 View 被设为 disable,那么它的 onTouch(),onClick(), onLongClick() 都不会被调用
  • 方法调用顺序 OnTouchListener.onTouch -> onTouchEvent -> ClickListener。onTouch() 返回了true,onTouchEvent() 就不会再被调用

2. ViewGroup 中的事件分发

ViewGroup 中可能包含多个子 View,其事件的分发逻辑自然就复杂一些,不过事件依然是从 dispatchTouchEvent() 开始分发。这个函数代码比较长,下面挑出关键代码,分段来看

public boolean dispatchTouchEvent(MotionEvent ev) {
//代码片段1
...
            // 如果是 DOWN 事件,清空之前的状态和事件的消费者
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                // Throw away all previous state when starting a new touch gesture.
                // The framework may have dropped the up or cancel event for the previous gesture
                // due to an app switch, ANR, or some other state change.
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            // 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;
            }
...
}
  1. 首先判断是否是 DOWN 事件,因为这意味着新一轮事件分发的开始,如果是 DOWN 事件,之前的保存的状态都需要重置(详见 cancelAndClearTouchTargets()resetTouchState() 方法)
  2. 接着要判断该 ViewGroup 是否要拦截事件,拦截事件意味着其子 View 都不会收到该事件,这里的判断逻辑比较复杂。首先需要搞清几个变量的作用:
  • mFirstTouchTarget,保存能够处理事件的子View,初始为 null,它其实是一个由 TouchTarget 组成的链表的头指针(为什么要用链表?一个事件序列应该最多只有一个目标View吧?没想明白...)。当 DOWN 事件到来时,也会清空链表并将该变量置为 null。为这个链表添加节点的操作在后面讲解;
  • FLAG_DISALLOW_INTERCEPT,该标志位标示子 View 是否允许其父ViewGroup 拦截事件,子 View 可以通过调用 requestDisallowInterceptTouchEvent() 来设置该标志位。但是在1.中已经提到,每当 DOWN 事件到来的时候会重置该标志位,所以当事件为 DOWN 时 disallowIntercept 值为 false
  1. disallowIntercept 为 false 就会进入下面的 if 判断,这里就会调用 onInterceptTouchEvent() 方法,这个方法默认返回 false,我们需要按需自己进行实现。从方法名也可看出这个函数是用来判断该 ViewGroup 是否拦截事件的,返回 ture 时代表拦截,反之则不拦截。
  2. 如果在 DOWN 事件时,onInterceptTouchEvent() 返回 true,即 ViewGroup 选择拦截事件 :
public boolean dispatchTouchEvent(MotionEvent ev) {
//代码片段2
    if (!canceled && !intercepted) {
    ...
    }

    if (mFirstTouchTarget == null) {
       // No touch targets so treat this as an ordinary view.
        handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
    } else {
       while (target != null) {
          final TouchTarget next = target.next;
          if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
               handled = true;
          } else {
               final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
               if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
                     handled = true;
               }
          }
       }// end while
       ...
    }
}

此时第一个 if 中的代码都不会被执行,第二个 if 中 mFirstTouchTarget == null条件成立,因为此时还没有任何可以处理事件的子View,所以就会执行 dispatchTransformedTouchEvent() 方法,注意到传入的第三个参数,也就是 child = null(见下面代码),所以最终调用 super.dispatchTouchEvent(),也就是在第一节中分析的 View.dispatchTouchEvent() 方法,也即 ViewGroup 自己拦截并处理了事件。

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) {
        final boolean handled;
...
       if (child == null) {
           handled = super.dispatchTouchEvent(event);
       } else {
           ...
           handled = child.dispatchTouchEvent(transformedEvent);
       }
       return handled;
      ...

接下来的 MOVE 和 UP 事件到来时,又一次进入代码片段1,此时第二个 if 中actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null 为 false,所以 onInterceptTouchEvent() 方法不会再调用,直接执行 intercepted = true,接下来都和 DOWN 事件一样了。所以 一旦VIewGroup 在 DOWN 事件进行了拦截,接下来的事件都会直接交给 ViewGroup 处理,子View也无法通过requestDisallowInterceptTouchEvent() 阻止 ViewGroup 拦截

  1. 如果在 DOWN 事件时,onInterceptTouchEvent() 返回 false ,此时代码片段3中的代码会执行:
public boolean dispatchTouchEvent(MotionEvent ev) {
//代码片段3
     if (!canceled && !intercepted) {
        ...
        if (actionMasked == MotionEvent.ACTION_DOWN 
               || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
               || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
           for (int i = childrenCount - 1; i >= 0; i--) {
               ...
               if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                   ...
                   newTouchTarget = addTouchTarget(child, idBitsToAssign);
                   break;
               }
               ...
           }//end for
        }
     }
}

这主要逻辑是在 for 循环中遍历子View,如果 dispatchTransformedTouchEvent() 方法返回 true,代表当前遍历的子View消耗事件(即子View的 dispatchTouchEvent() 返回 ture,参见上面的 dispatchTransformedTouchEvent()代码),则将其添加到 mFirstTouchTarget 指向的链表中(具体操作在 addTouchTarget()),然后直接break跳出循环。
回到代码片段2继续往下执行,如果前面找到了处理事件的子View,则 mFirstTouchTarget == null 为 false,else里的代码会被执行,主要逻辑就是遍历 mFirstTouchTarget 指向的链表里,并调用 dispatchTransformedTouchEvent() 把事件分发给子View。
之后的 MOVE 和 UP 事件到来时,又一次进入代码片段1,此时 mFirstTouchTarget != null,所以 actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null 为true,每次都会调用到 onInterceptTouchEvent(),所以 ViewGroup 都还有机会拦截事件。一旦ViewGroup 拦截了事件,子 View 都会立刻收到 CANCEL 事件,原因是代码片段2中 cancelChild = resetCancelNextUpFlag(target.child) || intercepted , cancelChild = true,传入 dispatchTransformedTouchEvent() 的第二个参数,就会导致子View收到 CANCEL 事件。

ViewGroup 的事件总结如下:

  • 如果在 DOWN 事件就拦截,即 onInterceptTouchEvent() 返回 true,那么之后onInterceptTouchEvent() 不会再调用,所有的后续事件都会直接交给 ViewGroup 处理
  • 如果在 DOWN 事件不拦截,那么后续所有的事件到来时,都会调用 onInterceptTouchEvent(),来询问 ViewGroup 是否要拦截,一旦 ViewGroup 决定拦截,之前接收事件的子 View 都会收到 CANCEL 事件。拦截的事件都会交给 ViewGroup 的 dispatchTouchEvent() 处理,之后的逻辑就是第一部分讲的 View 的事件分发逻辑
  • 如果子View在 DOWN 事件的时候选择不消耗事件,那么后续的事件都不会再传给子View,子View也没有机会再获取到事件,即使调用 requestDisallowInterceptTouchEvent() 也没用。

你可能感兴趣的:(Android事件分发机制源码分析及总结)