MotionEvent
      根据面向对象思想,事件被封装成 MotionEvent 对象,以下是几个与手指触摸相关的常见事件:
- ACTION_DOWN : 手指初次触摸到屏幕时触发。
 
- ACTION_MOVE:手指在屏幕上滑动时触发,会触发多次。
 
- ACTION_UP:手指离开屏幕时触发。
 
- ACTION_CANCEL:事件被上层拦截时触发。
 
对于单指操作,一次触摸事件流程是这样的:
按下(ACTION_DOWN)–> 滑动(ACTION_MOVE)–> 离开(ACTION_UP)。如果只是简单的点击,则没有 ACTION_MOVE 事件产生。
        
          事件分发、拦截与消费
      与事件分发相关的三个重要方法:
- dispatchTouchEvent:事件分发机制中的核心,所有的事件调度都归它管。
 
- onInterceptTouchEvent:事件拦截。
 
- onTouchEvent:事件消费处理。
 
        
          事件分发流程
      事件分发流程示意图:

大致解释一下:
图中 ViewGroup 与 View 之间省略了若干层 ViewGroup。
 
触摸事件都是先交由 Activity 的 dispatchTouchEvent 方法(在此之间还有一系列的操作,在此省略了),再一层层往下分发。当中间的 ViewGroup 不进行拦截时,事件会分发给最底层的 View,由 View 的 onTouchEvent 方法进行处理,如果事件一直未被处理,最后会返回到 Activity 的 onTouchEvent。
 
图中 View/ViewGroup 的 onTouchEvent 返回 false,并不是直接调用上层的 onTouchEvent 方法。而是上层的 dispatchTouchEvent 方法接收到下层的 false 返回值时,再将事件分发给自己的 onTouchEvent 处理。
 
onInterceptTouchEvent 只存在于 ViewGroup 中。ViewGroup 是根据 onInterceptTouchEvent 的返回值来确定是调用子 View 的 dispatchTouchEvent 还是自身的 onTouchEvent, 并没有将调用交给 onInterceptTouchEvent。
 
        
          源码分析
      事件是从 Activity 开始分发,Activity 的 dispatchTouchEvent 是如何接受到触摸事件,还有一系列的前期工作,后面会单独写一篇文章总结。
        
          Activity 对事件的分发流程
      
        
          Activity.dispatchTouchEvent
      1 2 3 4 5 6 7 8 9 10 11 12
   | public boolean dispatchTouchEvent(MotionEvent ev) {     if (ev.getAction() == MotionEvent.ACTION_DOWN) {                  onUserInteraction();     }          if (getWindow().superDispatchTouchEvent(ev)) {         return true;     }          return onTouchEvent(ev); }
  | 
 
其中 getWindow 返回的是 Activity 的 mWindow 成员变量,而 Window 类是一个抽象类,唯一实现类是 PhoneWindow,所以该方法获取到的是 PhoneWindow 对象。
        
          getWindow().superDispatchTouchEvent
      1 2 3
   | public boolean superDispatchTouchEvent(MotionEvent event) {     return mDecor.superDispatchTouchEvent(event);  }
  | 
 
可以看到,PhoneWindow 中直接将事件交给了 DecorView 处理,DecorView 的 superDispatchTouchEvent 方法如下。
1 2 3
   | public boolean superDispatchTouchEvent(MotionEvent event) {     return super.dispatchTouchEvent(event); }
  | 
 
DecorView 调用的是父类的 dispatchTouchEvent 方法,而 DecorView 的父类是 ViewGroup,所以接着会调用 ViewGroup.dispatchTouchEvent。
        
          Activity.onTouchEvent
      如果没有任何 View 处理事件,最后会交给 Activity 的 onTouchEvent 处理。
1 2 3 4 5 6 7 8
   | public boolean onTouchEvent(MotionEvent event) {          if (mWindow.shouldCloseOnTouch(this, event)) {         finish();         return true;     }     return false; }
  | 
 
        
          ViewGroup 对事件的分发流程
      
        
          ViewGroup.dispatchTouchEvent
      ViewGroup 的 dispatchTouchEvent 方法内容较多,这里拆分为检测拦截、寻找子 View、分发事件。
1、 检测拦截
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   |  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; }
 
  | 
 
这一段代码的目的是检测 ViewGroup 是否拦截事件。
mFirstTouchTarget 用来记录已经消费事件的子 View,目的是为了后续其他事件分发时直接将事件分发给 mFirstTouchTarget  指向的 View。
FLAG_DISALLOW_INTERCEPT 这个标志位可以影响到 ViewGroup 是否拦截事件,可以通过调用 requestDisallowInterceptTouchEvent 方法来设置,一般用于子 View 当中,禁止父 View 拦截事件,处理滑动冲突。但要注意,**requestDisallowInterceptTouchEvent 方法对 ACTION_DOWN 事件是无效的,为什么呢?因为 **ViewGroup 的 dispatchTouchEvent 方法每次接收到 ACTION_DOWN 事件时,都会初始化状态。代码如下:
1 2 3 4 5 6 7
   |  if (actionMasked == MotionEvent.ACTION_DOWN) {          cancelAndClearTouchTargets(ev);          resetTouchState(); }
 
  | 
 
2、寻找子 View
如果 ViewGroup 没有拦截事件,事件没有被取消,并且是 ACTION_DOWN 事件时,首先会去寻找可以接收事件的子 View。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
   |  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<View> preorderedList = buildTouchDispatchChildList();             final boolean customOrder = preorderedList == null                 && isChildrenDrawingOrderEnabled();             final View[] children = mChildren;                          for (int i = childrenCount - 1; i >= 0; i--) {                 final int childIndex = getAndVerifyPreorderedIndex(                     childrenCount, i, customOrder);                 final View child = getAndVerifyPreorderedView(                     preorderedList, children, 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;         }     } }
 
  | 
 
寻找子 View 进行分发事件的方法就是遍历子 View,有这样两个条件:
1 2 3 4 5 6
   |  if (!canViewReceivePointerEvents(child)     || !isTransformedTouchPointInView(x, y, child, null)) {     ev.setTargetAccessibilityFocus(false);     continue; }
 
  | 
 
这两个条件如果同时满足,则将事件分发给子 View。
接着会调用 dispatchTransformedTouchEvent 方法,可以猜到这个方法中肯定做了事件分发的操作。
如果这个方法返回 true,表示子 View 消费了事件,则会在 addTouchTarget 方法中设置 mFirstTouchTarget ,后续事件(ACTION_MOVE、ACTION_UP)分发时会直接将事件分发给 mFirstTouchTarget 指向的 View。
换句话说,如果子 View 没有消费 ACTION_DOWN 事件,mFirstTouchTarget 就会为 null,也就不会接收其他 ACTION_MOVE、ACTION_UP 等事件,如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
   | 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;             }             if (cancelChild) {                 if (predecessor == null) {                     mFirstTouchTarget = next;                 } else {                     predecessor.next = next;                 }                 target.recycle();                 target = next;                 continue;             }         }         predecessor = target;         target = next;     } }
  | 
 
3、事件分发
dispatchTransformedTouchEvent 的伪代码如下:
1 2 3 4 5
   | if (child == null) {     handled = super.dispatchTouchEvent(event);   } else {     handled = child.dispatchTouchEvent(event); }
  | 
 
如果不存在子 View,ViewGroup 会调用父类 View 的 dispatchTouchEvent 方法,再调用 onTouchEvent 方法处理事件。
如果存在子 View ,将事件分发给子 View 的 dispatchTouchEvent,子 View 如果是 ViewGroup,则会调用 ViewGroup.dispatchTouchEvent,进行拦截检测、寻找子 View、分发事件操作。
        
          View 对事件的分发流程
      
        
          View.dispatchTouchEvent
      1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
   | public boolean dispatchTouchEvent(MotionEvent event) {     ...          final int actionMasked = event.getActionMasked();     if (actionMasked == MotionEvent.ACTION_DOWN) {                  stopNestedScroll();     }
      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;          }     }
      if (!result && mInputEventConsistencyVerifier != null) {         mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);     }
           if (actionMasked == MotionEvent.ACTION_UP ||             actionMasked == MotionEvent.ACTION_CANCEL ||             (actionMasked == MotionEvent.ACTION_DOWN && !result)) {         stopNestedScroll();     }
      return result; }
  | 
 
如果存在 OnTouchListener,且视图状态为 ENABLED 时,调用onTouch 方法,onTouch 方法会优先处理事件。
如果 onTouch 方法返回 true,表示已经消费了事件,也就不再执行 onTouchEvent 。否则, onTouchEvent 处理事件,返回 true,消费事件,否则不处理事件。
        
          View.onTouchEvent
      1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
   | public boolean onTouchEvent(MotionEvent event) {     final float x = event.getX();     final float y = event.getY();     final int viewFlags = mViewFlags;
           if ((viewFlags & ENABLED_MASK) == DISABLED) {         if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {             setPressed(false);         }         return (((viewFlags & CLICKABLE) == CLICKABLE ||                 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));     }
      if (mTouchDelegate != null) {         if (mTouchDelegate.onTouchEvent(event)) {             return true;         }     }
                if (((viewFlags & CLICKABLE) == CLICKABLE ||             (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {         switch (event.getAction()) {             case MotionEvent.ACTION_UP:                 boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;                 if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {                     boolean focusTaken = false;                     if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {                         focusTaken = requestFocus();                     }
                      if (prepressed) {                         setPressed(true, x, y);                    }
                      if (!mHasPerformedLongPress) {                                                  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();                 }                 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();                 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; }
  | 
 
只要 view 是可点击或可长按,则消费该事件。
长按事件是在 ACTION_DOWN 事件中检测,单击事件需要两个事件 ACTION_DOWN、ACTION_UP 才能触发。
与 View 相关的各个方法调用顺序应该是这样的:
onTouchListener > onTouchEvent > onLongClickListener > onClickListener
        
          总结
      结合源码和事件分发示意图,对事件分发机制总结一下:
1、事件在从 Activity 的 dispatchTouchEvent 往下分发,如果没有 View 消费事件,事件最后会回到 Activity 的 onTouchEvent 方法处理。
2、ViewGroup 可以对事件进行拦截,拦截后事件不再往子 View 分发,交由发生拦截操作的 ViewGroup 的 onTouchEvent 处理。
3、子 View 可以调用 requestDisallowInterceptTouchEvent 方法进行设置,从而阻止父 ViewGroup 的 onInterceptTouchEvent 拦截事件。
4、如果 View 没有消费 ACTION_DOWN 事件,则之后的 ACTION_MOVE 等事件都不会再接收。
5、只要 View 是可点击或者可长按的,则消费该事件。
6、如果当前正在处理的事件被上层拦截,会收到一个 ACTION_CANCEL,后续事件不会再传递过来。
        
          参考