View 的事件分发机制(Android 开发艺术探索读书笔记)

1,106 阅读14分钟

-文章来源:itsCoder 的 WeeklyBolg 项目

前言

在写这篇笔记的时候想了好久,也拖了好长时间,关于事件分发的博客看了很多,有的写的思路很清晰,画了事件分发的整体流程图,但是没有源码,看过之后只能知道事件是怎么分发的,但完全是记住的,而不是通过源码分析出来的,试想,如果以后再遇到其他知识点还是这样,那么我们就完全成了不能靠自己去分析问题,只能去食他人知识,没有自我学习分析能力,所以笔者试着结合艺术探索的讲解,尝试在源码的基础上加以理解,本文的写作逻辑是先从文字描述上尽量让大家先大概了解,事件分发的概况,先有个感性认识,再结合源码进行分析,如果错误的地方,还请指出。

1.1 点击事件的传递规则

在我们进行分析事件分发机制之前,先思考一下我们要研究哪些问题:

1、事件分发的对象是什么?

当用户触摸屏幕时,将创建一个 MotionEvent 对象即点击事件。MotionEvent 包含关于发生触摸的位置、时间、历史记录、手势动作等细节信息, Touch 事件相关细节被封装成了 MotionEvent 对象。理解了这一个知识点后,其实我们就很容易理解所谓点击事件的分发,其实就是对 MotionEvent 事件分发的过程,即当一个 MotionEvent 产生后,系统需要把这个事件传递给一个具体的 View 去处理, 而这个传递的过程就是分发过程。

2、事件是在哪些对象之间传递?

Android 与用户交互的界面就是由一系列 Activity(Fragment)、ViewGroup、View 组成如图:

Activity_ViewGroup_View

当然一个界面可能由多个ViewGroup或者多个View 组成,所以事件就是在这三者之间进行传递,我们要分析的就是要捋清楚事件是由哪个对象发出,经过哪些对象,最终达到哪个对象,在某些条件改变的时候下他们之间的关系又是怎样的,理解了这些之后,当我们在遇到点击或者滑动事件冲突的情况,相信一切问题就迎刃而解了。

3、这个分发过程由哪些对象协作完成?

其实点击事件分发过程主要由三个重要方法共同完成:dispatchTouchEvent(MotionEvent event) 、onInterceptTouchEvent(MotionEvent event)和onTouchEvent(MotionEvent event),下面先介绍一下这三个方法的主要作用,这样有利于后面我们对源码的分析:

  1. public boolean dispatchTouchEvent(MotionEvent event) :用来进行事件的分发。如果事件能够传递给当前view,那么此方法一定会被调用,返回结果受当前 view 的 onTouchEvent 和下级 view 的 dispatchTouchEvent 方法的影响,表示是否消耗当前事件。返回值 true,表示触摸事件被消费,已经分发出去,后续事件会继续分发到该 View;返回值 false,则表示触摸事件没有被消费,即事件没有分发出去,那么后续事件就不会继续向该 View 分发,该方法在 View 和 ViewGroup 中都有。

  2. public boolean onInterceptTouchEvent(MotionEvent event)
    在 dispatchTouchEvent 方法内部调用,用来判断是否拦截某个事件,如果当前 view 拦截了某个事件,那么在同一个事件序列当中,此方法不会再被调用,返回结果表示是否拦截当前事件。只有 ViewGroup 中才有该方法 ,返回值 true,表示ViewGroup拦截了该触摸事件,该事件就不会分发给它的子 View 或者子 ViewGroup,事件会由自己的 onTouchEvent() 方法处理。返回值 false,表示 ViewGroup 没有拦截该事件,该事件就可以分发给它的子 View 和子 ViewGroup ,事件传递到子 view 的 dispatchTouchEvent() 方法中去处理。

  3. public boolean onTouchEvent(MotionEvent event)
    在 dispatchTouchEvent 方法内部调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果 ACTION_DOWN不消耗,则在同一个事件序列中,当前 view 无法再次接收到事件。返回值为 True ,事件由自己处理,后续事件序列让其处理;返回值为 False ,自己不消耗事件,向上返回让其他的父容器的onTouchEvent接受处理。

    这三个方法都是通过 dispatchTouchEvent() 联系在一起,他们之间的关系可以用如下伪代码表示:

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

结论:对于一个根 ViewGroup,点击事件产生后,首先会传递给它,这时 ViewGroup 的 dispatchTouchEvent 会调用,而在 dispatchTouchEvent 方法中会调用 onInterceptTouchEvent() 方法,如果它的 onInterceptTouchEvent 返回 true 表示要拦截当前事件,接下来事件会交给这个 ViewGroup 处理,此时 ViewGroup 的 onTouchEvent 方法就会被调用,如果这个ViewGroup 的 onInterceptTouchEvent 返回 false,则事件会继续传递给子元素,子元素的 dispatchTouchEvent 会调用,如此反复直到事件被处理。

当一个View需要处理事件时,如果设置了 OnTouchListener ,那么 OnTouchListener 的 onTouch方法会回调,如果 onTouch 返回 false,则当前 View 的 onTouchEvent 方法会被调用;如果返回 true,那么 onTouchEvent 方法将不会调用。由此可见,OnTouchListener 优先级高于 onTouchEvent。OnClickListener 优先级处在事件传递的尾端。

4、事件的传递顺序是什么?

这里就直接给出答案:一个点击事件产生后,传递顺序:Activity -> Window -> ViewGroup -> View;如果一个 View 的 onTouchEvent 返回 false 即 View 没有处理事件,那么它的父容器的onTouchEvent 会被调用,以此类推,所有元素都不处理该事件,最终将传递给 Activity 处理,即 Activity 的 onTouchEvent 会被调用,事件顺序为:View -> ViewGroup -> Window -> Activity。

5、关于事件传递的其他一些结论这里先给出,以便我们更好的理解时间传递机制,结论如下:

  1. 同一个事件序列是指从手指触摸屏幕那一刻开始,中间包含数量不定的 move 事件到手指离开屏幕那一刻(down->move…move->up)。
  2. 正常情况下一个事件序列只能被一个 View 拦截且消耗,每个 View 一旦决定拦截,同一个事件序列所有事件都会直接交给它处理,并且它的 onInterceptTouchEvent 不会再被调用。
  3. 某个 View 一旦开始处理事件,如果它不消耗 ACTION_DOWN( onTouchEvent 返回了 false ),那么同一事件序列中其他事件都不会再交给它来处理,事件将重新交给他的父元素处理,即父元素的 onTouchEvent 会被调用。
  4. 如果某个 View 不消耗除 ACTION_DOWN 以外的其他事件,那么这个点击事件会消失,此时父元素的 onTouchEvent 并不会被调用,并且当前 View 可以收到后续事件,最终这些消失的点击事件会传递给 Activity 处理。
  5. ViewGroup 默认不拦截任何事件,ViewGroup 的 onInterceptTouchEvent 方法默认返回 false。
  6. View 没有 onInterceptTouchEvent 方法,一旦有事件传递给它,那么它的 onTouchEvent 方法就会被调用。
  7. View 的 onTouchEvent 方法默认消耗事件(返回 true ),除非他是不可点击的( clickable 和 longClickable 同时为 false )。View 的 longClickable 属性默认都为 false ,clickable 属性分情况,Button 默认为 true,TextView 默认为 false。
  8. onClick 发生的前提是View可点击,并且它收到了down 和 up 事件。
  9. 事件传递过程是由外而内,事件总是先传递给父元素,然后在由父元素分发给子 View,通过requestDisallowInterceptTouchEvent 方法可以在子元素干预父元素的事件分发过程,但 ACTION_DOWN 事件除外。

1.2 事件分发的源码解析

这里要分别分析 Activity 、ViewGroup 和 View 对事件的分发。

1. Activity 对点击事件的分发过程

由于平时我们对 Activity 事件分发接触不是很多(笔者是这样),使用应该不是很多,所以这里只做简单介绍。

(1) Activity 中与触摸事件相关API主要是 dispatchTouchEvent() 和 onTouchEvent()。dispatchTouchEvent() 是传递触摸事件的API,而 onTouchEvent() 则是 Activity 处理触摸事件的API。

(2) Activity 中的 dispatchTouchEven 会将触摸事件传递给Activity 所包含的视图。具体的实现方式在通过调用到 Activity 所属 Window 的 superDispatchTouchEvent,进而调用到 Window 的 DecorView 的 superDispatchTouchEvent,进一步又调用到 ViewGroup 的 dispatchTouchEvent() ,这样事件就从 Activity 传递到了 ViewGroup 。
如果 Activity 所包含的视图拦截或者消费了该触摸事件的话,就不会再执行 Activity 的 onTouchEvent() ;
如果 Activity 所包含的视图没有拦截或者消费该触摸事件的话,则会执行 Activity 的 onTouchEvent() 。
(3) Activity 中的 onTouchEvent 是 Activity 自身对触摸事件的处理。如果该 Activity 的 android:windowCloseOnTouchOutside 属性为 true,并且当前触摸事件是 ACTION_DOWN ,而且该触摸事件的坐标在 Activity 之外,同时 Activity 还包含了视图的话就会导致 Activity 被结束。


2. View 对点击事件的处理过程

这里的 View 不包含 ViewGroup ,这里说下为什么先分析 View 而不是像书上那样先分析 ViewGroup ,因为在 ViewGroup 的事件分发过程中会调用到 View 对事件的处理,而且 View 的事件处理相比 ViewGroup 而言简单些,所以这里先分析 View 。

我们来看一下 View 中 dispatchTouchEvent 方法的源码:

 public boolean dispatchTouchEvent (MotionEvent event){
        .........
        boolean result = false;
        ...........
        if (onFilterTouchEventForSecurity(event)) {
            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;
            .............
    return result;

从上面源码的10行代码可以看出,首先会判断 mOnTouchListener 是否为空,而mOnTouchListener 是在 setOnTouchListener 方法里赋值的,也就是说只要我们给控件注册了touch 事件,mOnTouchListener 就一定不为空,而(mViewFlags & ENABLED_MASK) == ENABLED 是判断当前点击的控件是否是 enable 的,按钮默认都是 enable 的,因此这个条件恒定为 true。mOnTouchListener.onTouch(this, event),其实也就是去回调控件注册 touch 事件时的 onTouch 方法。也就是说如果我们在 onTouch 方法里返回 true,就会让这三个条件全部成立,从而整个方法返回 true。在结合22行代码 if (!result && onTouchEvent(event)),可以得出结论,如果我们在 onTouch 方法里返回 false,就会再去执行 onTouchEvent(event)方法,如果 onTouch 方法里返回 true ,onTouchEvent(event) 方法将不会执行, 可见 OnTouchListener 优先级高于 onTouchEvent(event) 方法。
接下来我们分析一下 onTouchEvent(event) 方法的源码:

  public boolean onTouchEvent(MotionEvent event) {   
final float x = event.getX();
      final float y = event.getY();
      final int viewFlags = mViewFlags;
      final int action = event.getAction();
      if ((viewFlags & ENABLED_MASK) == DISABLED) {
          if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
              setPressed(false);
          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:
                  boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                  if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepresse
                      boolean focusTaken = false;
                      if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                          focusTaken = requestFocus();
                      if (prepressed) {
                          setPressed(true, x, y);
                      if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                          removeLongPressCallback();
                          if (!focusTaken) {
                              if (mPerformClick == null) {
                                  mPerformClick = new PerformClick();
                              if (!post(mPerformClick)) {
                                  performClick();
                      if (mUnsetPressedState == null) {
                          mUnsetPressedState = new UnsetPressedState();
                      if (prepressed) {
                          postDelayed(mUnsetPressedState,
                                  ViewConfiguration.getPressedStateDuration());
                      } else if (!post(mUnsetPressedState)) {
                          mUnsetPressedState.run();
                      removeTapCallback();
                  mIgnoreNextUpEvent = false;
                  break;
              case MotionEvent.ACTION_DOWN:
                  mHasPerformedLongPress = false;
                  if (performButtonActionOnTouchDown(event)) {
                      break;
                  boolean isInScrollingContainer = isInScrollingContainer();
                  if (isInScrollingContainer) {
                      mPrivateFlags |= PFLAG_PREPRESSED;
                      if (mPendingCheckForTap == null) {
                          mPendingCheckForTap = new CheckForTap();
                      mPendingCheckForTap.x = event.getX();
                      mPendingCheckForTap.y = event.getY();
                      postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                  } else {
                      setPressed(true, x, y);
                      checkForLongClick(0);
                  break;
              case MotionEvent.ACTION_CANCEL:
                  setPressed(false);
                  removeTapCallback();
                  removeLongPressCallback();
                  mInContextButtonPress = false;
                  mHasPerformedLongPress = false;
                  mIgnoreNextUpEvent = false;
                  break;
              case MotionEvent.ACTION_MOVE:
                  drawableHotspotChanged(x, y);
                  if (!pointInView(x, y, mTouchSlop)) {
                      removeTapCallback();
                      if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                          removeLongPressCallback();
                          setPressed(false);
                  break;
          return true;
      return false;

先看第7行代码 if ((viewFlags & ENABLED_MASK) == DISABLED) 这个条件是判断 view 是不可用状态,而在该条件下我们看第17行代码 如下:

return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
 || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);

这个返回结果是判断只要 view 是可点击状态 那么返回值就问 true ,即如果 view 是不可用状态,但只要 view 是可点击状态,就会消耗点击事件,只不过不响应结果。
再看第33行会判断只要 view 是可点击的,那么就会进入到下面的 switch 语句,最终在第123行返回 true,即只要 view 是可点击的,那么 ontouchEvent(Event v) 方法就会返回 true 消费点击事件。
然后在 ACTION_UP 事件触发的时候,在55行代码会执行 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;
   sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
     return result;

上面第6行代码 if (li != null && li.mOnClickListener != null) 判断如果给当前 view 设置了点击事件,那么就会执行回调设置的 onClick() 方法即 mOnClickListener.onClick(this),这样 view 的 dispatchTouchEvent(MotionEvent event) 这里就分析结束了!

3 ViewGroup 对点击事件的处理过程

同样我们来看一下 ViewGroup 中 dispatchTouchEvent 方法的源码:

public boolean dispatchTouchEvent(MotionEvent ev) {
  ............
  boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
    final int action = ev.getAction();
    final int actionMasked = action & MotionEvent.ACTION_MASK;
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        cancelAndClearTouchTargets(ev);
        resetTouchState();
    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); 
        } else {
            intercepted = false;
    } else {
        intercepted = true;
    ..............
    TouchTarget newTouchTarget = null;
    boolean alreadyDispatchedToNewTouchTarget = false;
    if (!canceled && !intercepted) {
        View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                ? findChildWithAccessibilityFocus() : null;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            final int actionIndex = ev.getActionIndex(); 
            final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                    : TouchTarget.ALL_POINTER_IDS;
            removePointersFromTouchTargets(idBitsToAssign);
            final int childrenCount = mChildrenCount;
            if (newTouchTarget == null && childrenCount != 0) {
                final float x = ev.getX(actionIndex);
                final float y = ev.getY(actionIndex);
                final ArrayList preorderedList = buildOrderedChildList();
                final boolean customOrder = preorderedList == null
                        && isChildrenDrawingOrderEnabled();
                final View[] children = mChildren;
                for (int i = childrenCount - 1; i >= 0; i--) {
                    final int childIndex = customOrder
                            ? getChildDrawingOrder(childrenCount, i) : i;
                    final View child = (preorderedList == null)
                            ? children[childIndex] : preorderedList.get(childIndex);
                    if (childWithAccessibilityFocus != null) {
                        if (childWithAccessibilityFocus != child) {
                            continue;
                        childWithAccessibilityFocus = null;
                        i = childrenCount - 1;
                    if (!canViewReceivePointerEvents(child)
                            || !isTransformedTouchPointInView(x, y, child, null)) {
                        ev.setTargetAccessibilityFocus(false);
                        continue;
                    newTouchTarget = getTouchTarget(child);
                    if (newTouchTarget != null) {
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                        break;
                    resetCancelNextUpFlag(child);
                    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                        mLastTouchDownTime = ev.getDownTime();
                        if (preorderedList != null) {
                            for (int j = 0; j < childrenCount; j++) {
                                if (children[childIndex] == mChildren[j]) {
                                    mLastTouchDownIndex = j;
                                    break;
                        } else {
                            mLastTouchDownIndex = childIndex;
                        mLastTouchDownX = ev.getX();
                        mLastTouchDownY = ev.getY();
                        newTouchTarget = addTouchTarget(child, idBitsToAssign);
                        alreadyDispatchedToNewTouchTarget = true;
                        break;
                    ev.setTargetAccessibilityFocus(false);
                if (preorderedList != null) preorderedList.clear();
            if (newTouchTarget == null && mFirstTouchTarget != null) {
                newTouchTarget = mFirstTouchTarget;
                while (newTouchTarget.next != null) {
                    newTouchTarget = newTouchTarget.next;
                newTouchTarget.pointerIdBits |= idBitsToAssign;
    if (mFirstTouchTarget == null) {
        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                TouchTarget.ALL_POINTER_IDS);
    } else {
        TouchTarget predecessor = null;
        TouchTarget target = mFirstTouchTarget;
        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;
                ..........
                return handled;

事件分发主要分为以上几个步骤,代码中已经标出,下面加以总结:

在上面执行第2步的时候,ViewGroup 在两种情况下会判断是否拦截事件即 事件类型为 ACTION_DOWN 和 mFirstTouchTarget != null 条件下, ACTION_DOWN 好理解,那么 mFirstTouchTarget != null 是什么意思呢,我们看一下在执行第5步,事件由 ViewGroup 子 View 处理成功的时候有一行代码是:newTouchTarget = addTouchTarget(child, idBitsToAssign);这行代码实现方法如下:

     private TouchTarget addTouchTarget(View child, int pointerIdBits) {
    TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
    target.next = mFirstTouchTarget;
    mFirstTouchTarget = target;
    return target;

通过上面代码可以看出,事件由 ViewGroup 子 View 处理成功的时候 mFirstTouchTarget 将会被赋值,即事件分发到子 view 的时候mFirstTouchTarget != null,反过来如果事件由 ViewGroup 进行拦截,那么mFirstTouchTarget != null 就不成立,那么当 ACTION_MOVE 和 ACIONT_UP 事件到来的时候 if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) 条件为 false,将导致 ViewGroup 的 onInterceptTouchEvent(ev) 方法不再调用,同一系列事件中的其他事件将由 ViewGroup 进行处理。当然有一种特殊情况,就是 FLAG_DISALLOW_INTERCEPT 这个标记位,这个标记位是子 view 调用了 requestDisallowInterceptTouchEvent() 来进行设置,ViewGroup 将无法进行拦截除了 ACTION_DOWN 以外的其他事件,这是因为在进行 ACTION_DOWN 的时候,会重置 FLAG_DISALLOW_INTERCEPT 这个标记位,这样导致子 view 设置的 requestDisallowInterceptTouchEvent() 失效。

在看一下第5步,是如何调用 dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign) 方法进行事件分发的,这行代码实现方法如下:

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

我们只看与我们要分析目标相关的代码,上面代码中我们看到,如果 child!=null ,那么执行 handled = child.dispatchTouchEvent(event) 之后的逻辑就是前面分析的 view 的事件分发,这样就完成了事件分发到子 view 的流程,我们看 child.dispatchTouchEvent(event) 是由返回值的,即如果返回 true ,事件由子 view 消费,事件分发成功,跳出第4步 for 循环,停止遍历子 view。

我们再看一下第7步,如果 mFirstTouchTarget == null ,说明没有任何子 view 接受触摸事件,那么调用 handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS) 注意这里第三个参数传入 null,就是我们上面分析的另一种情况 ,如果 child == null ,执行 handled = super.dispatchTouchEvent(event) ,而 ViewGroup 的父类是 view ,所以之后的处理逻辑又和前面说的 view 一样了,即 ViewGroup 的 onTouch 方法会得到执行 ,而如果 mFirstTouchTarget != null ,表明已经有事件分发成功了,那么会执行 while (target != null) 循环从从链表 mFirstTouchTarget 中取出 target,条件 if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) 如果成立,那么说明这个事件是是已经分发过成功的事件,那么直接执行 handled = true ,即让 dispatchTouchEvent 方法返回 true, 表明事件分发成功,而如果 if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) 不成立,表明该事件是新到来的事件还没进行分发,那么执行 dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) ,进一步对事件进行分发,并将 分发处理结果进行返回,这这情况下就是前面 第3步所说,不执行第三步,直接执行第7步对事件进一步就行分发。

最后说了这么多,大家可以结合下面这张流程图来对整理分发流程进行梳理,图片引自:

图解 Android 事件分发机制

TouchEvent_disaptch

其中 super 表示调用关系,true 表示消费事件, false 表示没有消费事件。

到这里关于 View 的事件分发分析就结束了!

如果感觉笔者写的不好,可以参考以下博客:

郭神的 Android事件分发机制完全解析,带你从源码的角度彻底理解(下)

skywangkw 的Android 触摸事件机制(四) ViewGroup中触摸事件详解