Android 事件分发机制源码解析

发布于 2025-05-08 08:23:56 字数 11071 浏览 0 评论 0

更简单的学习 Android 事件分发 中,使用日志、比喻、流程图相结合的方式,以更简单的方法去分析了 Android 的事件分发机制。本篇文章将采用分析源码的方式,更深入的解析 Android 的事件分发机制。

一、从 Activity 开始

Android 的触摸事件,是由 windowManagerService 进行采集,之后传递到 Activiy 进行处理。我们这里从 Activity#dispatchTouchEvent 方法开始解析

public boolean dispatchTouchEvent(MotionEvent ev) {
  if (ev.getAction() == MotionEvent.ACTION_DOWN) {
    onUserInteraction();
  }
  if (getWindow().superDispatchTouchEvent(ev)) {
    return true;
  }
  return onTouchEvent(ev);
}

上述代码中, onUserInteraction() 是一个空的实现,我们直接来看下 getWindow().superDispatchTouchEvent(ev) 方法。window 是一个抽象的方法,不过系统给它提供了一个实现类 PhoneWindow,我们这里看下它的 superDispatchTouchEvent(ev) 方法。

public boolean superDispatchTouchEvent(MotionEvent event) {
  return mDecor.superDispatchTouchEvent(event);
}

上述代码调用了 DecorView 类的 superDispatchTouchEvent 方法,继续跟进

public boolean superDispatchTouchEvent(MotionEvent event) {
  return super.dispatchTouchEvent(event);
}

上述代码调用了父类的 dispatchTouchEvent 方法,DecorView 的父类为 FrameLayout,其直接继承了 ViewGroup#dispatchTouchEvent 方法。

二、ViewGroup 中的事件分发

ViewGroup#dispatchTouchEvent 方法比较长,这里只截取部分进行分析

public boolean dispatchTouchEvent(MotionEvent ev) {

  ...
    // 在 ACTION_DOWN 事件时,初始化 Touch 标记
    // 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.
    final boolean intercepted;
    if (actionMasked == MotionEvent.ACTION_DOWN
        || mFirstTouchTarget != null) {
      // 是否拦截的标志位,假如设置 requestDisallowInterceptTouchEvent(true),
      // 则为 true,不拦截事件
      final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
      if (!disallowIntercept) {
        // 默认返回 false
        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;
    }

    ...

    // Check for cancelation.
    final boolean canceled = resetCancelNextUpFlag(this)
        || actionMasked == MotionEvent.ACTION_CANCEL;

    // Update list of touch targets for pointer down, if needed.
    final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
    TouchTarget newTouchTarget = null;
    boolean alreadyDispatchedToNewTouchTarget = false;
    // 不是 ACTION_CANCEL 事件,并且不拦截事件
    if (!canceled && !intercepted) {

      // If the event is targeting accessiiblity 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.getX(actionIndex);
          final float y = ev.getY(actionIndex);
          // Find a child that can receive the event.
          // Scan children from front to back.
          final ArrayList<View> preorderedList = buildOrderedChildList();
          final boolean customOrder = preorderedList == null
              && isChildrenDrawingOrderEnabled();
          final View[] children = mChildren;
          // 遍历所有子 View
          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);

            ...

            resetCancelNextUpFlag(child);
            // 把事件(ACTION_DOWN、ACTION_POINTER_DOWN、ACTION_HOVER_MOVE) 传递给子 View 处理
            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 = ev.getX();
              mLastTouchDownY = ev.getY();
              newTouchTarget = addTouchTarget(child, idBitsToAssign);
              alreadyDispatchedToNewTouchTarget = true;
              break;
            }
            ...
          }
          ...
        }
        ...
        }
      }
    }

    // 分发事件到目标 View
    // Dispatch to touch targets.
    if (mFirstTouchTarget == null) {
      // 没有找到事件分发目标的情况,将会调用自己的 onTouchEvent 方法
      // 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;
        // ACTION_DOWN 已经完成事件分发,并消费了事件,直接返回 true
        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
          handled = true;
        } else {
          final boolean cancelChild = resetCancelNextUpFlag(target.child)
              || intercepted;
          // 其余事件则需要传递给目标 View 进行处理
          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;
      }
    }

    // 对 ACTION_CANCEL 事件进行处理
    // Update list of touch targets for pointer up or cancel, if needed.
    if (canceled
        || actionMasked == MotionEvent.ACTION_UP
        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
      // 重置 Touch 状态
      resetTouchState();
    } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
      final int actionIndex = ev.getActionIndex();
      final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
      removePointersFromTouchTargets(idBitsToRemove);
    }
  }

  ...
  return handled;
}

// 默认返回 false
public boolean onInterceptTouchEvent(MotionEvent ev) {
  return false;
}

我们现在来看看传递事件的 dispatchTransformedTouchEvent 方法,同样我也只是截取了其中比较关键的部分

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
    View child, int desiredPointerIdBits) {
  final boolean handled;
  ...
  final MotionEvent transformedEvent;
  // 对 transformedEvent 的一系列计算
  ...
  if (child == null) {
    // 如果没有子 View,则执行 super.dispatchTouchEvent 方法,
    // 调用自己的 onTouchEvent 方法
    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());
    }
    // 如果有子 View,则调用子 View#dispatchTouchEvent 方法
    handled = child.dispatchTouchEvent(transformedEvent);
  }

  // Done.
  transformedEvent.recycle();
  return handled;
}

三、View 中的事件处理

ViewGroup 中不拦截事件,调用子 View#dispatchTouchEvent 方法进行处理

public boolean dispatchTouchEvent(MotionEvent event) {

  ...
  if (onFilterTouchEventForSecurity(event)) {
    // 如果设置了 OnTouchListener,使用 onTouch 对事件进行处理,
    // 并返回 true,则不需要再执行 onTouchEvent 方法
    //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;
    }
  }
  ...

  return result;
}

这里继续看看 View#onTouchEvent 方法

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 & CLICKABLE) == CLICKABLE ||
      (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
      (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
    switch (action) {
      case MotionEvent.ACTION_UP:
        ...
        // 移除长按
        removeLongPressCallback();
        ...
          // 检查单击
          performClick();
        ...
        break;

      case MotionEvent.ACTION_DOWN:
        ...
        // 检测是否为长按
        checkForLongClick(0);
        ...
        break;

      ....
    }

    return true;
  }

  return false;
}

上述代码,主要是检查 View 是否可以点击,如果可点击,则会返回 true,同时也会执行可点击的事件。

四、小结

通过本文的源码解析,我们可以更深入的理解 Android 的事件分发。可以简单的推出一个流程 : Activity→PhoneWindow→DecorView→ViewGroup→View。如果在阅读过程中,有任何疑问与问题,欢迎与我联系。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

壹場煙雨

暂无简介

文章
评论
32 人气
更多

推荐作者

画骨成沙

文章 0 评论 0

微信用户

文章 0 评论 0

缘字诀

文章 0 评论 0

蓝眼泪

文章 0 评论 0

man_creator

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。