Android 事件分发

83 阅读10分钟
  • 滑动最新单位像素getScaledTouchSlop

  • 关于事件三大方法

    • dispatchTouchEvent
    • onInterceptTouchEventViewGroup dispatchTouchEvent内部会调用
    • onTouchEventdispatchTouchEvent内部会调用
    • onTouch
  • 先看一下事件分发核心源代码,基本是 ViewGroup 事件分发的起点 dispatchTouchEvent 方法,方法中会调用到onInterceptTouchEvent 方法,询问是否需要自己拦截处理,也会间接调用到 onTouchEvent 方法,接下来一点点拆解关键地方。

    • @Override
      public boolean dispatchTouchEvent(MotionEvent ev) {
          if (mInputEventConsistencyVerifier != null) {
              mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
          }
      
          // If the event targets the accessibility focused view and this is it, start
          // normal event dispatch. Maybe a descendant is what will handle the click.
          if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
              ev.setTargetAccessibilityFocus(false);
          }
      
          boolean handled = false;
          if (onFilterTouchEventForSecurity(ev)) {
              final int action = ev.getAction();
              final int actionMasked = action & MotionEvent.ACTION_MASK;
      
              // Handle an initial 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.
              // 关键点1
              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 intercepted, start normal event dispatch. Also if there is already
              // a view that is handling the gesture, do normal event dispatch.
              if (intercepted || mFirstTouchTarget != null) {
                  ev.setTargetAccessibilityFocus(false);
              }
      
              // Check for cancelation.
              final boolean canceled = resetCancelNextUpFlag(this)
                      || actionMasked == MotionEvent.ACTION_CANCEL;
      
              // Update list of touch targets for pointer down, if needed.
              final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE;
              final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0
                      && !isMouseEvent;
              TouchTarget newTouchTarget = null;
              boolean alreadyDispatchedToNewTouchTarget = false;
              if (!canceled && !intercepted) {
                  // If the event is targeting accessibility focus we give it to the
                  // view that has accessibility focus and if it does not handle it
                  // we clear the flag and dispatch the event to all children as usual.
                  // We are looking up the accessibility focused host to avoid keeping
                  // state since these events are very rare.
                  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(); // always 0 for down
                      final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                              : TouchTarget.ALL_POINTER_IDS;
      
                      // Clean up earlier touch targets for this pointer id in case they
                      // have become out of sync.
                      removePointersFromTouchTargets(idBitsToAssign);
      
                      final int childrenCount = mChildrenCount;
                      if (newTouchTarget == null && childrenCount != 0) {
                          final float x = ev.getXDispatchLocation(actionIndex);
                          final float y = ev.getYDispatchLocation(actionIndex);
                          // Find a child that can receive the event.
                          // Scan children from front to back.
                          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 there is a view that has accessibility focus we want it
                              // to get the event first and if not handled we will perform a
                              // normal dispatch. We may do a double iteration but this is
                              // safer given the timeframe.
                              if (childWithAccessibilityFocus != null) {
                                  if (childWithAccessibilityFocus != child) {
                                      continue;
                                  }
                                  childWithAccessibilityFocus = null;
                                  i = childrenCount;
                              }
      
                              if (!child.canReceivePointerEvents()
                                      || !isTransformedTouchPointInView(x, y, child, null)) {
                                  ev.setTargetAccessibilityFocus(false);
                                  continue;
                              }
      
                              newTouchTarget = getTouchTarget(child);
                              if (newTouchTarget != null) {
                                  // Child is already receiving touch within its bounds.
                                  // Give it the new pointer in addition to the ones it is handling.
                                  newTouchTarget.pointerIdBits |= idBitsToAssign;
                                  break;
                              }
      
                              resetCancelNextUpFlag(child);
                              // 关键点2
                              if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                  // Child wants to receive touch within its bounds.
                                  mLastTouchDownTime = ev.getDownTime();
                                  if (preorderedList != null) {
                                      // childIndex points into presorted list, find original index
                                      for (int j = 0; j < childrenCount; j++) {
                                          if (children[childIndex] == mChildren[j]) {
                                              mLastTouchDownIndex = j;
                                              break;
                                          }
                                      }
                                  } else {
                                      mLastTouchDownIndex = childIndex;
                                  }
                                  mLastTouchDownX = x;
                                  mLastTouchDownY = y;
                                  // 关键点3
                                  newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                  alreadyDispatchedToNewTouchTarget = true;
                                  break;
                              }
      
                              // The accessibility focus didn't handle the event, so clear
                              // the flag and do a normal dispatch to all children.
                              ev.setTargetAccessibilityFocus(false);
                          }
                          if (preorderedList != null) preorderedList.clear();
                      }
      
                      if (newTouchTarget == null && mFirstTouchTarget != null) {
                          // Did not find a child to receive the event.
                          // Assign the pointer to the least recently added target.
                          newTouchTarget = mFirstTouchTarget;
                          while (newTouchTarget.next != null) {
                              newTouchTarget = newTouchTarget.next;
                          }
                          newTouchTarget.pointerIdBits |= idBitsToAssign;
                      }
                  }
              }
      
              // Dispatch to touch targets.
              if (mFirstTouchTarget == null) {
                  // No touch targets so treat this as an ordinary view.
                  handled = dispatchTransformedTouchEvent(ev, canceled, null,
                          TouchTarget.ALL_POINTER_IDS);
              } else {
                  // Dispatch to touch targets, excluding the new touch target if we already
                  // dispatched to it.  Cancel touch targets if necessary.
                  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;
                  }
              }
      
              // Update list of touch targets for pointer up or cancel, if needed.
              if (canceled
                      || actionMasked == MotionEvent.ACTION_UP
      || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                  resetTouchState();
              } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
                  final int actionIndex = ev.getActionIndex();
                  final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
                  removePointersFromTouchTargets(idBitsToRemove);
              }
          }
      
          if (!handled && mInputEventConsistencyVerifier != null) {
              mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
          }
          return handled;
      }
      
  • ViewGroup 中询问是否需要拦截,ViewGroup 具备优先处理事件权利,需要自己处理,就可以直接拦截掉

    • // 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 方法,事件就不会继续

public boolean onInterceptTouchEvent(MotionEvent ev) {
    if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
            && ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
            && isOnScrollbarThumb(ev.getXDispatchLocation(0), ev.getYDispatchLocation(0))) {
        return true;
    }
    return false;
}

  拦截掉后,就不会循环询问子 View 逻辑了 ,跳过下面查找逻辑,查找逻辑无非就找到一个匹配消费事件的 TouchTarget 一般只有在 ACTION_DOWN 的情况下,才会查找,ACTION_UPACTION_MOVE 不会查找

if (!canceled && !intercepted) {
}

  mFirstTouchTarget = null 调用 dispatchTransformedTouchEvent 方法最后直接走 View 分发逻辑 super.dispatchTouchEvent(event) ,在 View dispatchTouchEvent 方法中,最终调用回 ViewGroup onTouchEvent 方法

// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
    // No touch targets so treat this as an ordinary view.
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
}
if (child == null) {
    handled = super.dispatchTouchEvent(event);
}
  • 刚讲到的是关于 ViewGroup 拦截掉事件的情况,接下来分析不拦截的情况,不拦截其实就是分发到下面的子View 处理,关于分发到哪个子 View 会有一个循环查找的逻辑,外层是循环遍历子ViewisTransformedTouchPointInView方法主要是看点击区域是否在该子View区域内,dispatchTransformedTouchEvent方法是调用子View分发逻辑,看看该子View是否消费

    • if (actionMasked == MotionEvent.ACTION_DOWN
      || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
              || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
          final int actionIndex = ev.getActionIndex(); // always 0 for down
          final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                  : TouchTarget.ALL_POINTER_IDS;
      
          // Clean up earlier touch targets for this pointer id in case they
          // have become out of sync.
          removePointersFromTouchTargets(idBitsToAssign);
      
          final int childrenCount = mChildrenCount;
          if (newTouchTarget == null && childrenCount != 0) {
              final float x = ev.getXDispatchLocation(actionIndex);
              final float y = ev.getYDispatchLocation(actionIndex);
              // Find a child that can receive the event.
              // Scan children from front to back.
              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 there is a view that has accessibility focus we want it
                  // to get the event first and if not handled we will perform a
                  // normal dispatch. We may do a double iteration but this is
                  // safer given the timeframe.
                  if (childWithAccessibilityFocus != null) {
                      if (childWithAccessibilityFocus != child) {
                          continue;
                      }
                      childWithAccessibilityFocus = null;
                      i = childrenCount;
                  }
      
                  if (!child.canReceivePointerEvents()
                          || !isTransformedTouchPointInView(x, y, child, null)) {
                      ev.setTargetAccessibilityFocus(false);
                      continue;
                  }
      
                  newTouchTarget = getTouchTarget(child);
                  if (newTouchTarget != null) {
                      // Child is already receiving touch within its bounds.
                      // Give it the new pointer in addition to the ones it is handling.
                      newTouchTarget.pointerIdBits |= idBitsToAssign;
                      break;
                  }
      
                  resetCancelNextUpFlag(child);
                  if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                      // Child wants to receive touch within its bounds.
                      mLastTouchDownTime = ev.getDownTime();
                      if (preorderedList != null) {
                          // childIndex points into presorted list, find original index
                          for (int j = 0; j < childrenCount; j++) {
                              if (children[childIndex] == mChildren[j]) {
                                  mLastTouchDownIndex = j;
                                  break;
                              }
                          }
                      } else {
                          mLastTouchDownIndex = childIndex;
                      }
                      mLastTouchDownX = x;
                      mLastTouchDownY = y;
                      newTouchTarget = addTouchTarget(child, idBitsToAssign);
                      alreadyDispatchedToNewTouchTarget = true;
                      break;
                  }
      
                  // The accessibility focus didn't handle the event, so clear
                  // the flag and do a normal dispatch to all children.
                  ev.setTargetAccessibilityFocus(false);
              }
              if (preorderedList != null) preorderedList.clear();
          }
      
          if (newTouchTarget == null && mFirstTouchTarget != null) {
              // Did not find a child to receive the event.
              // Assign the pointer to the least recently added target.
              newTouchTarget = mFirstTouchTarget;
              while (newTouchTarget.next != null) {
                  newTouchTarget = newTouchTarget.next;
              }
              newTouchTarget.pointerIdBits |= idBitsToAssign;
          }
      }
      
    •   dispatchTransformedTouchEvent 方法中 如果child不为空,就调用子View分发逻辑 child.dispatchTouchEvent(event),找到合适的子View之后,会存储下TouchTarget一个单向链表,当所有子View都不消费的时候,还是会继续回到这个方法,ViewGroup自己调用会View分发逻辑,最后闭环。
    • private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
              View child, int desiredPointerIdBits) {
          final boolean handled;
      
          // Canceling motions is a special case.  We don't need to perform any transformations
          // or filtering.  The important part is the action, not the contents.
          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;
          }
      
          // Calculate the number of pointers to deliver.
          final int oldPointerIdBits = event.getPointerIdBits();
          final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
      
          // If for some reason we ended up in an inconsistent state where it looks like we
          // might produce a motion event with no pointers in it, then drop the event.
          if (newPointerIdBits == 0) {
              return false;
          }
      
          // If the number of pointers is the same and we don't need to perform any fancy
          // irreversible transformations, then we can reuse the motion event for this
          // dispatch as long as we are careful to revert any changes we make.
          // Otherwise we need to make a copy.
          final MotionEvent transformedEvent;
          if (newPointerIdBits == oldPointerIdBits) {
              if (child == null || child.hasIdentityMatrix()) {
                  if (child == null) {
                      handled = super.dispatchTouchEvent(event);
                  } else {
                      final float offsetX = mScrollX - child.mLeft;
                      final float offsetY = mScrollY - child.mTop;
                      event.offsetLocation(offsetX, offsetY);
      
                      handled = child.dispatchTouchEvent(event);
      
                      event.offsetLocation(-offsetX, -offsetY);
                  }
                  return handled;
              }
              transformedEvent = MotionEvent.obtain(event);
          } else {
              transformedEvent = event.split(newPointerIdBits);
          }
      
          // Perform any necessary transformations and dispatch.
          if (child == null) {
              handled = super.dispatchTouchEvent(transformedEvent);
          } else {
              final float offsetX = mScrollX - child.mLeft;
              final float offsetY = mScrollY - child.mTop;
              transformedEvent.offsetLocation(offsetX, offsetY);
              if (! child.hasIdentityMatrix()) {
                  transformedEvent.transform(child.getInverseMatrix());
              }
      
              handled = child.dispatchTouchEvent(transformedEvent);
          }
      
          // Done.
          transformedEvent.recycle();
          return handled;
      }
      
  • ACTION_DOWN的时候,如果找到了一个合适消费子View,会存储到一个TouchTarget链表中,事件是一系列执行的,连续的,ACTION_MOVEACTION_UP,会直接跳过查找逻辑了,直接分发事件dispatchTransformedTouchEventalreadyDispatchedToNewTouchTarget变量只有ACTION_DOWN,并且第一次找到子View,才会等于true

    • 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;
          }
      }
      
  • 再来看看onTouchEvent方法,该方法是由dispatchTouchEvent方法调用,其中如果View是*CLICKABLE,LONG_CLICKABLE,CONTEXT_CLICKABLE默认是可以消费事件的。否则不会消费事件,比如但是当一个ViewGroup* ,被调用*setOnClickListener之后,它会修改成功CLICKABLE*

    • public void setOnClickListener(@Nullable OnClickListener l) {
          if (!isClickable()) {
              setClickable(true);
          }
          getListenerInfo().mOnClickListener = l;
      }
      
    • public boolean onTouchEvent(MotionEvent event) {
          final float x = event.getX();
          final float y = event.getY();
          final int viewFlags = mViewFlags;
          final int action = event.getAction();
      
          final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
      || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                  || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
      
          if ((viewFlags & ENABLED_MASK) == DISABLED
      && (mPrivateFlags4 & PFLAG4_ALLOW_CLICK_WHEN_DISABLED) == 0) {
              if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                  setPressed(false);
              }
              mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
              // A disabled view that is clickable still consumes the touch
              // events, it just doesn't respond to them.
              return clickable;
          }
          if (mTouchDelegate != null) {
              if (mTouchDelegate.onTouchEvent(event)) {
                  return true;
              }
          }
      
          if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
              switch (action) {
                  case MotionEvent.ACTION_UP:
                      mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                      if ((viewFlags & TOOLTIP) == TOOLTIP) {
                          handleTooltipUp();
                      }
                      if (!clickable) {
                          removeTapCallback();
                          removeLongPressCallback();
                          mInContextButtonPress = false;
                          mHasPerformedLongPress = false;
                          mIgnoreNextUpEvent = false;
                          break;
                      }
                      boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                      if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                          // take focus if we don't have it already and we should in
                          // touch mode.
                          boolean focusTaken = false;
                          if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                              focusTaken = requestFocus();
                          }
      
                          if (prepressed) {
                              // The button is being released before we actually
                              // showed it as pressed.  Make it show the pressed
                              // state now (before scheduling the click) to ensure
                              // the user sees it.
                              setPressed(true, x, y);
                          }
      
                          if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                              // This is a tap, so remove the longpress check
                              removeLongPressCallback();
      
                              // 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)) {
                                      performClickInternal();
                                  }
                              }
                          }
      
                          if (mUnsetPressedState == null) {
                              mUnsetPressedState = new UnsetPressedState();
                          }
      
                          if (prepressed) {
                              postDelayed(mUnsetPressedState,
                                      ViewConfiguration.getPressedStateDuration());
                          } else if (!post(mUnsetPressedState)) {
                              // If the post failed, unpress right now
                              mUnsetPressedState.run();
                          }
      
                          removeTapCallback();
                      }
                      mIgnoreNextUpEvent = false;
                      break;
      
                  case MotionEvent.ACTION_DOWN:
                      if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                          mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                      }
                      mHasPerformedLongPress = false;
      
                      if (!clickable) {
                          checkForLongClick(
                                  ViewConfiguration.getLongPressTimeout(),
                                  x,
                                  y,
                                  TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                          break;
                      }
      
                      if (performButtonActionOnTouchDown(event)) {
                          break;
                      }
      
                      // Walk up the hierarchy to determine if we're inside a scrolling container.
                      boolean isInScrollingContainer = isInScrollingContainer();
      
                      // For views inside a scrolling container, delay the pressed feedback for
                      // a short period in case this is a scroll.
                      if (isInScrollingContainer) {
                          mPrivateFlags |= PFLAG_PREPRESSED;
                          if (mPendingCheckForTap == null) {
                              mPendingCheckForTap = new CheckForTap();
                          }
                          mPendingCheckForTap.x = event.getX();
                          mPendingCheckForTap.y = event.getY();
                          postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                      } else {
                          // Not inside a scrolling container, so show the feedback right away
                          setPressed(true, x, y);
                          checkForLongClick(
                                  ViewConfiguration.getLongPressTimeout(),
                                  x,
                                  y,
                                  TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                      }
                      break;
      
                  case MotionEvent.ACTION_CANCEL:
                      if (clickable) {
                          setPressed(false);
                      }
                      removeTapCallback();
                      removeLongPressCallback();
                      mInContextButtonPress = false;
                      mHasPerformedLongPress = false;
                      mIgnoreNextUpEvent = false;
                      mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                      break;
      
                  case MotionEvent.ACTION_MOVE:
                      if (clickable) {
                          drawableHotspotChanged(x, y);
                      }
      
                      final int motionClassification = event.getClassification();
                      final boolean ambiguousGesture =
                              motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
                      int touchSlop = mTouchSlop;
                      if (ambiguousGesture && hasPendingLongPressCallback()) {
                          if (!pointInView(x, y, touchSlop)) {
                              // The default action here is to cancel long press. But instead, we
                              // just extend the timeout here, in case the classification
                              // stays ambiguous.
                              removeLongPressCallback();
                              long delay = (long) (ViewConfiguration.getLongPressTimeout()
                                      * mAmbiguousGestureMultiplier);
                              // Subtract the time already spent
                              delay -= event.getEventTime() - event.getDownTime();
                              checkForLongClick(
                                      delay,
                                      x,
                                      y,
                                      TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                          }
                          touchSlop *= mAmbiguousGestureMultiplier;
                      }
      
                      // Be lenient about moving outside of buttons
                      if (!pointInView(x, y, touchSlop)) {
                          // Outside button
                          // Remove any future long press/tap checks
                          removeTapCallback();
                          removeLongPressCallback();
                          if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                              setPressed(false);
                          }
                          mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                      }
      
                      final boolean deepPress =
                              motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
                      if (deepPress && hasPendingLongPressCallback()) {
                          // process the long click action immediately
                          removeLongPressCallback();
                          checkForLongClick(
                                  0 /* send immediately */,
                                  x,
                                  y,
                                  TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
                      }
      
                      break;
              }
      
              return true;
          }
      
          return false;
      }
      
  • View 中onTouch 优先级高于 onTouchEvent,如果onTouch消费了,就不会走onTouchEvent方法

    • 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;
          }
      }
      
  • ACTION_UP事件发生时,会触发performClick方法

    • if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
          // This is a tap, so remove the longpress check
          removeLongPressCallback();
      
          // 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)) {
                  performClickInternal();
              }
          }
      }
      
  • View 源码中有大量位运算符

& 运算 如果两个操作数的对应位都为1,则结果位为1;否则,结果位为0

int a = 9;    // 二进制表示为 1001
int b = 5;    // 二进制表示为 0101
int result = a & b;    // 二进制结果为 0001,十进制结果为 1
System.out.println(result);    // 输出: 1

|运算 对两个二进制数的每一位进行逻辑或运算,结果为1的位只需要两个数的对应位中有一个为1即可

int a = 5;    // 二进制表示为 0101
int b = 3;    // 二进制表示为 0011
int c = a | c;  // 二进制结果为 0111,十进制结果为:7
System.out.println(result);    // 输出: 7
  • 事件流转都是从父 ViewGroup 开始,优先父 View 考虑是否消费,默认是不消费的,不消费再继续循环查找子 View 是否要消费,子View 不消费,会继续回到父 View
  • 事件只有在第一次ACTION_DOWN 的时候才会去查找子View是否要消费,找到后会放入到一个TouchTarget 链表中存储起来,在ACTION_MOVE的时候,直接处理。