触摸事件分发机制

什么是触摸事件?

在 Android 中,事件(Event)是用户与设备交互的输入信号,它可能来自触摸屏、按键、轨迹球等。对于触摸操作,事件由 MotionEvent 封装,包含了动作类型(如按下、移动、抬起)以及位置、时间等信息。

从手指触摸屏幕到抬起的整个过程中会产生一系列事件,称作事件序列,通常包含:

  1. 一个 ACTION_DOWN(手指按下,序列开始);

  2. 若干个 ACTION_MOVE(手指滑动);

  3. 一个 ACTION_UP(手指抬起,序列结束)或 ACTION_CANCEL(事件被系统中断)。

事件分发机制简述

事件分发机制是指 Android 在接收到一个输入事件后,如何将该事件从顶层容器逐级传递到最终的 View 进行处理的过程。

主要涉及三个核心方法:

  • dispatchTouchEvent(MotionEvent ev):事件分发,决定事件的去向;

  • onInterceptTouchEvent(MotionEvent ev)(ViewGroup 特有):事件拦截,决定是否阻止子 View 接收事件;

  • onTouchEvent(MotionEvent ev):事件处理,决定是否消费事件。

分发机制的基本原则:

  • 事件总是先传到最外层容器(Activity → Window → DecorView → ViewGroup)

  • ViewGroup 可以选择拦截事件,不再向子 View 分发。

  • View 接到事件后可以消费(返回 true)或不消费(返回 false)。

  • 一旦某个 View 消费了 ACTION_DOWN,后续的事件序列会直接分发给它。

事件分发的自底向上过程

事件从哪里来?

在聊具体的事件分发机制之前,先搞清楚一个问题:用户触摸屏幕后,事件是如何到达 Activity 的?

  1. 用户手指触摸屏幕 → 硬件驱动检测到坐标变化;

  2. InputManagerService (IMS) 从底层驱动读取输入事件,并找到当前有焦点的窗口;

  3. 通过 Binder IPC 将事件传递到应用进程,对应的 ViewRootImpl 负责接收事件;

  4. ViewRootImpl 调用 DecorView.dispatchTouchEvent(),而 DecorView 会回调 Window.Callback 接口;

  5. Window.Callback 的实现类正是 Activity,所以事件最终到达 Activity.dispatchTouchEvent(),这也是我们能接触到的第一个事件入口。

Activity

Activity 中,dispatchTouchEvent() 是触摸事件分发的入口方法:

// Activity.java
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction(); // 空方法,标记一次新的触摸序列开始
    }
    // 交给 Window(PhoneWindow)处理
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    // 如果 Window 没消费,自己尝试处理
    return onTouchEvent(ev);
}
  • onUserInteraction() 默认是空实现,系统在检测到新的 ACTION_DOWN 时会调用它,供开发者重写(比如在锁屏、休眠逻辑中)。

  • 真正的事件分发是交给 Window 去做的,Activity 自身只在 Window 没消费事件时兜底调用 onTouchEvent()

Window

Window 是一个抽象类,Activity 在 attach() 方法中创建的实际是 PhoneWindow

// Activity.java
final void attach(Context context, ...) {
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setCallback(this); // Activity 自身作为回调处理事件
}

Window 的分发实现很简单,自身并不处理事件,只是桥梁,把事件转交给它的顶层 View(DecorView):

// PhoneWindow.java
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

DecorView

DecorView 是应用窗口中 View 树的根节点,继承自 FrameLayout,它会在 Activity 第一次调用 setContentView() 时由 PhoneWindow 创建。它直接将事件传递给其父类 ViewGroup 进行向下分发:

// DecorView.java
public boolean superDispatchTouchEvent(MotionEvent event) {
    // 调用父类 ViewGroup 的 dispatchTouchEvent 方法
    return super.dispatchTouchEvent(event); 
}

小结

至此,一个触摸事件的向上传递链路是:

Activity.dispatchTouchEvent()
   → Window.superDispatchTouchEvent()
       → DecorView.superDispatchTouchEvent()
           → ViewGroup.dispatchTouchEvent()

事件分发的自顶向下过程

当触摸事件从 Activity 向上传递到 ViewGroup 之后,就进入了 ViewGroup 的事件分发流程。ViewGroup 作为容器类,不仅可以处理事件,还能决定是否把事件交给子 View,这就是所谓的“自顶向下”分发。

ViewGroup 事件分发

ViewGroup.dispatchTouchEvent() 是事件分发的核心逻辑,伪代码如下:

// ViewGroup.java
public boolean dispatchTouchEvent(MotionEvent ev) {
    boolean handled = false;

    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        // 清空之前的触摸状态,准备新一轮事件分发
        cancelAndClearTouchTargets();
    }

    // 1. 判断是否拦截
    boolean intercept = onInterceptTouchEvent(ev);

    // 2. 如果不拦截,找到触摸点命中的子 View
    if (!intercept) {
        View child = findChildUnder(ev.getX(), ev.getY());
        if (child != null) {
            // 分发给子 View 处理
            handled = child.dispatchTouchEvent(ev);
        }
    }

    // 3. 如果拦截了,或者子 View 没处理,就交给自己处理
    if (!handled) {
        handled = onTouchEvent(ev);
    }

    return handled;
}

onInterceptTouchEvent() 是 ViewGroup 特有的方法,决定是否拦截当前事件序列。

  • 返回 true:事件不再传给子 View,改由 ViewGroup 自己处理。

  • 返回 false:事件传递给子 View。

系统默认返回 false,因此像 LinearLayout 这样的基础容器通常不会主动拦截。

View 分发事件

对于普通 View,事件分发逻辑更简单。核心代码可以概括为:

// —— View:触摸事件分发(精简版)——
public boolean dispatchTouchEvent(MotionEvent event) {
    boolean handled = false;
    int action = event.getActionMasked();

    // 1) 新手势开始,防御性清理嵌套滚动状态
    if (action == MotionEvent.ACTION_DOWN) {
        stopNestedScroll();
    }

    // 2) 安全过滤(如窗口安全标志/来源校验等),不过滤才继续处理
    if (onFilterTouchEventForSecurity(event)) {
        // 3) 主通路:优先 OnTouchListener;否则走 onTouchEvent()
        handled = performOnTouchCallback(event);
    }

    // 4) 收尾:手势结束或 DOWN 未被消费 → 停止嵌套滚动,避免残留影响
    if (action == MotionEvent.ACTION_UP
            || action == MotionEvent.ACTION_CANCEL
            || (action == MotionEvent.ACTION_DOWN && !handled)) {
        stopNestedScroll();
    }

    // 5) 返回是否已被当前 View 消费
    return handled;
}

performOnTouchCallback() 的核心逻辑如下(保留重点):

// —— 优先监听器,未消费再走 onTouchEvent ——
// 说明:监听器需要 view.isEnabled() 才会触发(系统默认语义)
private boolean performOnTouchCallback(MotionEvent event) {
    // A. OnTouchListener 优先
    if (mOnTouchListener != null && isEnabled()
            && mOnTouchListener.onTouch(this, event)) {
        return true; // 监听器已消费
    }
    // B. 监听器未消费 → 交给 onTouchEvent 兜底
    return onTouchEvent(event);
}

performOnTouchCallback() 的源码可以发现:

  • 设置了 OnTouchListener 并且返回 true → 事件就不会走到 onTouchEvent

  • 返回 false 或没有设置 OnTouchListener → 事件会继续走 onTouchEvent

小结

  • ViewGroup:通过 onInterceptTouchEvent 决定事件是自己处理还是交给子 View;

  • View:通过 OnTouchListeneronTouchEvent 来消费事件,监听器优先。

View 绘制流程

自定义 View

RecyclerView