Android View 触摸事件分发机制

Android View 触摸事件分发机制

基于 AOSP 源码(Android 15/V)的深入分析


目录

  • [1. 概述](#1. 概述)
    • [1.1 Android 输入事件体系的整体架构](#1.1 Android 输入事件体系的整体架构)
    • [1.2 触摸事件(MotionEvent)类型说明](#1.2 触摸事件(MotionEvent)类型说明)
  • [2. 事件分发的设计架构](#2. 事件分发的设计架构)
    • [2.1 责任链模式的应用](#2.1 责任链模式的应用)
    • [2.2 完整事件传递链路](#2.2 完整事件传递链路)
    • [2.3 核心类关系图](#2.3 核心类关系图)
  • [3. 事件分发详细流程](#3. 事件分发详细流程)
    • [3.1 ViewRootImpl 接收事件的流程(InputStage 管道)](#3.1 ViewRootImpl 接收事件的流程(InputStage 管道))
    • [3.2 Activity.dispatchTouchEvent() 的流程](#3.2 Activity.dispatchTouchEvent() 的流程)
    • [3.3 ViewGroup.dispatchTouchEvent() 的详细流程](#3.3 ViewGroup.dispatchTouchEvent() 的详细流程)
    • [3.4 View.dispatchTouchEvent() 的流程](#3.4 View.dispatchTouchEvent() 的流程)
    • [3.5 View.onTouchEvent() 的流程](#3.5 View.onTouchEvent() 的流程)
    • [3.6 ViewGroup.dispatchTouchEvent 流程图](#3.6 ViewGroup.dispatchTouchEvent 流程图)
    • [3.7 完整触摸事件时序图](#3.7 完整触摸事件时序图)
  • [4. 事件冲突的解决方案](#4. 事件冲突的解决方案)
    • [4.1 外部拦截法](#4.1 外部拦截法)
    • [4.2 内部拦截法](#4.2 内部拦截法)
    • [4.3 FLAG_DISALLOW_INTERCEPT 的工作原理](#4.3 FLAG_DISALLOW_INTERCEPT 的工作原理)
    • [4.4 嵌套滑动机制(NestedScrolling)简介](#4.4 嵌套滑动机制(NestedScrolling)简介)
    • [4.5 常见冲突场景及解决方案](#4.5 常见冲突场景及解决方案)
    • [4.6 外部拦截法与内部拦截法对比流程图](#4.6 外部拦截法与内部拦截法对比流程图)
  • [5. 关键源码解析](#5. 关键源码解析)
    • [5.1 ViewRootImpl.WindowInputEventReceiver](#5.1 ViewRootImpl.WindowInputEventReceiver)
    • [5.2 ViewRootImpl.ViewPostImeInputStage.processPointerEvent](#5.2 ViewRootImpl.ViewPostImeInputStage.processPointerEvent)
    • [5.3 View.dispatchPointerEvent](#5.3 View.dispatchPointerEvent)
    • [5.4 DecorView.dispatchTouchEvent](#5.4 DecorView.dispatchTouchEvent)
    • [5.5 Activity.dispatchTouchEvent](#5.5 Activity.dispatchTouchEvent)
    • [5.6 PhoneWindow.superDispatchTouchEvent](#5.6 PhoneWindow.superDispatchTouchEvent)
    • [5.7 View.dispatchTouchEvent](#5.7 View.dispatchTouchEvent)
    • [5.8 View.performOnTouchCallback](#5.8 View.performOnTouchCallback)
    • [5.9 ViewGroup.dispatchTouchEvent](#5.9 ViewGroup.dispatchTouchEvent)
    • [5.10 ViewGroup.dispatchTransformedTouchEvent](#5.10 ViewGroup.dispatchTransformedTouchEvent)
    • [5.11 ViewGroup.onInterceptTouchEvent](#5.11 ViewGroup.onInterceptTouchEvent)
    • [5.12 View.onTouchEvent](#5.12 View.onTouchEvent)
    • [5.13 TouchTarget 内部类](#5.13 TouchTarget 内部类)
    • [5.14 ViewGroup.requestDisallowInterceptTouchEvent](#5.14 ViewGroup.requestDisallowInterceptTouchEvent)
  • [6. 多点触控的事件分发](#6. 多点触控的事件分发)
    • [6.1 MotionEvent 中 Pointer 的概念](#6.1 MotionEvent 中 Pointer 的概念)
    • [6.2 ACTION_POINTER_DOWN/UP 的分发逻辑](#6.2 ACTION_POINTER_DOWN/UP 的分发逻辑)
    • [6.3 多个 TouchTarget 对应不同子 View](#6.3 多个 TouchTarget 对应不同子 View)

1. 概述

1.1 Android 输入事件体系的整体架构

Android 的输入事件处理贯穿从硬件驱动到应用层的完整软件栈,整体分为以下层次:

  1. 硬件层 :触摸屏控制器产生原始触摸数据,通过 Linux 内核的 input 子系统(/dev/input/eventX)暴露给用户空间。

  2. Native 层(InputManagerService / InputDispatcher)

    • InputReader:从 EventHub 读取原始事件,将其转换为 RawEvent,再经过 InputMapper 映射为 NotifyMotionArgs
    • InputDispatcher:负责将事件分发到正确的窗口。它通过 InputChannel(基于 Unix socket pair)与应用进程通信。
  3. Framework Java 层(ViewRootImpl)

    • InputEventReceiver:应用进程端接收事件的底层入口,通过 JNI 回调从 native 层接收事件。
    • ViewRootImpl:管理 View 树的根节点,内部通过 InputStage 管道 处理事件。
    • 事件最终通过 mView.dispatchPointerEvent() 进入 View 树。
  4. View 体系(DecorView -> Activity -> ViewGroup -> View)

    • DecorView:窗口的根 View,继承自 FrameLayout(ViewGroup)。
    • Activity:作为 Window.Callback,参与事件分发链。
    • ViewGroup:通过 dispatchTouchEvent()onInterceptTouchEvent() 实现事件的递归分发与拦截。
    • View:通过 dispatchTouchEvent()onTouchEvent() 实现事件的最终处理。

1.2 触摸事件(MotionEvent)类型说明

触摸事件定义在 MotionEvent.java 中,关键常量如下:

常量 说明
ACTION_DOWN 0 第一个手指按下,一个触摸手势的开始
ACTION_UP 1 最后一个手指抬起,一个触摸手势的结束
ACTION_MOVE 2 手指在屏幕上滑动
ACTION_CANCEL 3 当前手势被取消,不应执行任何动作
ACTION_OUTSIDE 4 触摸发生在 UI 元素的正常边界之外
ACTION_POINTER_DOWN 5 非第一个手指按下(多点触控)
ACTION_POINTER_UP 6 非最后一个手指抬起(多点触控)

源码位置:frameworks/base/core/java/android/view/MotionEvent.java 第 219-289 行

一次完整的单指触摸手势的事件序列为:ACTION_DOWN -> ACTION_MOVE(0..N) -> ACTION_UP

一次多指触摸手势的事件序列为:ACTION_DOWN -> ACTION_POINTER_DOWN -> ACTION_MOVE(0..N) -> ACTION_POINTER_UP -> ACTION_UP


2. 事件分发的设计架构

2.1 责任链模式的应用

Android 触摸事件分发机制采用了**责任链模式(Chain of Responsibility)**的变体。其核心特征:

  1. 自顶向下传递:事件从 Activity 开始,经过 ViewGroup 递归传递到子 View。
  2. 自底向上消费:如果子 View 不消费事件,事件逐级回传给父 ViewGroup 处理。
  3. 拦截机制 :ViewGroup 可以通过 onInterceptTouchEvent() 在传递过程中截断事件链。
  4. 一次决定、后续绑定ACTION_DOWN 时确定事件的目标 View(TouchTarget),后续同一手势的 ACTION_MOVE/ACTION_UP 直接发送给该目标。

这种设计将"谁来处理事件"的决策分散在 View 树的各个节点上,每个 ViewGroup 都可以独立决定是否拦截事件,每个 View 都可以独立决定是否消费事件。

2.2 完整事件传递链路

事件从底层到应用层的完整路径:

复制代码
InputManagerService (Native)
    |
    v (通过 InputChannel / socket pair)
InputEventReceiver.onInputEvent() (Java)
    |
    v
ViewRootImpl.enqueueInputEvent()
    |
    v
ViewRootImpl.InputStage 管道 (NativePreIme -> ViewPreIme -> Ime -> EarlyPostIme -> NativePostIme -> ViewPostIme -> Synthetic)
    |
    v
ViewPostImeInputStage.processPointerEvent()
    |
    v
mView.dispatchPointerEvent()  // mView 就是 DecorView
    |
    v
DecorView.dispatchTouchEvent()  // 获取 Window.Callback(即 Activity)
    |
    v
Activity.dispatchTouchEvent()  // Window.Callback.dispatchTouchEvent
    |
    v
PhoneWindow.superDispatchTouchEvent()
    |
    v
DecorView.superDispatchTouchEvent()  // 调用 super.dispatchTouchEvent,即 ViewGroup.dispatchTouchEvent
    |
    v
ViewGroup.dispatchTouchEvent()  // 递归分发给子 View
    |
    v
View.dispatchTouchEvent() -> onTouchEvent()

2.3 核心类关系图

继承
继承(FrameLayout)
继承
继承
继承
实现 Window.Callback
包含(链表)
包含(管道)
包含
mView(DecorView)
mWindow
mDecor
mWindow
View
-mOnTouchListener: OnTouchListener
+dispatchTouchEvent(MotionEvent) : boolean
+onTouchEvent(MotionEvent) : boolean
+dispatchPointerEvent(MotionEvent) : boolean
+setOnTouchListener(OnTouchListener)
-performOnTouchCallback(MotionEvent) : boolean
ViewGroup
-mFirstTouchTarget: TouchTarget
-mGroupFlags: int
+FLAG_DISALLOW_INTERCEPT: int
+dispatchTouchEvent(MotionEvent) : boolean
+onInterceptTouchEvent(MotionEvent) : boolean
+requestDisallowInterceptTouchEvent(boolean)
-dispatchTransformedTouchEvent(MotionEvent, boolean, View, int) : boolean
-addTouchTarget(View, int) : TouchTarget
-cancelAndClearTouchTargets(MotionEvent)
-resetTouchState()
-buildTouchDispatchChildList() : ArrayList<View>
-isTransformedTouchPointInView(float, float, View, PointF) : boolean
TouchTarget
+child: View
+pointerIdBits: int
+next: TouchTarget
+ALL_POINTER_IDS: int
+obtain(View, int) : TouchTarget
+recycle()
ViewRootImpl
-mView: View
-mFirstInputStage: InputStage
-mFirstPostImeInputStage: InputStage
-mInputEventReceiver: WindowInputEventReceiver
+enqueueInputEvent(InputEvent, InputEventReceiver, int, boolean)
-doProcessInputEvents()
<<abstract>>
InputStage
-mNext: InputStage
#FORWARD: int
#FINISH_HANDLED: int
#FINISH_NOT_HANDLED: int
+deliver(QueuedInputEvent)
#onProcess(QueuedInputEvent) : int
ViewPostImeInputStage
#onProcess(QueuedInputEvent) : int
-processPointerEvent(QueuedInputEvent) : int
WindowInputEventReceiver
+onInputEvent(InputEvent)
+onBatchedInputEventPending(int)
<<abstract>>
InputEventReceiver
-mInputChannel: InputChannel
-mMessageQueue: MessageQueue
+onInputEvent(InputEvent)
Activity
-mWindow: Window
+dispatchTouchEvent(MotionEvent) : boolean
+onTouchEvent(MotionEvent) : boolean
<<abstract>>
Window
+superDispatchTouchEvent(MotionEvent) : boolean
+setCallback(Callback)
+getCallback() : Callback
PhoneWindow
-mDecor: DecorView
+superDispatchTouchEvent(MotionEvent) : boolean
DecorView
-mWindow: PhoneWindow
+dispatchTouchEvent(MotionEvent) : boolean
+superDispatchTouchEvent(MotionEvent) : boolean
+onInterceptTouchEvent(MotionEvent) : boolean
<<interface>>
WindowCallback
+dispatchTouchEvent(MotionEvent) : boolean


3. 事件分发详细流程

3.1 ViewRootImpl 接收事件的流程(InputStage 管道)

ViewRootImpl 在其 setView() 方法中构建了一条 InputStage 管道,事件按以下顺序经过各个 Stage:

复制代码
NativePreImeInputStage -> ViewPreImeInputStage -> ImeInputStage
    -> EarlyPostImeInputStage -> NativePostImeInputStage
    -> ViewPostImeInputStage -> SyntheticInputStage

源码位置:frameworks/base/core/java/android/view/ViewRootImpl.java 第 1742-1754 行

java 复制代码
// 第 1742-1754 行
mSyntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
        "aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
InputStage imeStage = new ImeInputStage(earlyPostImeStage,
        "aq:ime:" + counterSuffix);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
        "aq:native-pre-ime:" + counterSuffix);

mFirstInputStage = nativePreImeStage;
mFirstPostImeInputStage = earlyPostImeStage;

事件进入流程

  1. WindowInputEventReceiver.onInputEvent() 被 native 层回调(第 10440 行)
  2. 调用 enqueueInputEvent() 将事件加入队列(第 10461 行)
  3. doProcessInputEvents() 从队列取出事件,调用 deliverInputEvent()
  4. 事件经过 InputStage 管道,在 ViewPostImeInputStage.onProcess() 中处理触摸事件
  5. processPointerEvent() 调用 mView.dispatchPointerEvent(event)(第 8031 行)
  6. View.dispatchPointerEvent() 判断是触摸事件后调用 dispatchTouchEvent()(第 16742-16743 行)

InputStage 基类的核心机制(第 7151-7186 行):

每个 InputStage 通过 deliver() 方法处理事件,内部调用 onProcess() 获取处理结果:

  • FORWARD:不处理,转发给下一个 Stage
  • FINISH_HANDLED:已处理
  • FINISH_NOT_HANDLED:未处理但也不转发

3.2 Activity.dispatchTouchEvent() 的流程

源码位置:frameworks/base/core/java/android/app/Activity.java 第 4535-4543 行

java 复制代码
// Activity.java 第 4535-4543 行
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

流程说明:

  1. 如果是 ACTION_DOWN 事件,先调用 onUserInteraction() 通知有用户交互(可用于重置屏幕超时等)。
  2. 调用 getWindow().superDispatchTouchEvent(ev),即 PhoneWindow.superDispatchTouchEvent(),将事件传递给 View 树。
  3. 如果 View 树没有消费事件(返回 false),则调用 Activity.onTouchEvent() 作为最后的处理机会。

Activity.onTouchEvent() 的默认实现(第 4285-4292 行):

java 复制代码
// Activity.java 第 4285-4292 行
public boolean onTouchEvent(MotionEvent event) {
    if (mWindow.shouldCloseOnTouch(this, event)) {
        finish();
        return true;
    }
    return false;
}

默认实现仅处理一种情况:如果触摸发生在窗口范围外且窗口是可关闭的(如 Dialog 风格的 Activity),则关闭窗口。

3.3 ViewGroup.dispatchTouchEvent() 的详细流程

这是整个事件分发机制中最核心、最复杂的方法。

源码位置:frameworks/base/core/java/android/view/ViewGroup.java 第 2635-2853 行

整个方法分为以下关键阶段:

阶段一:ACTION_DOWN 时的初始化(第 2652-2658 行)
java 复制代码
if (actionMasked == MotionEvent.ACTION_DOWN) {
    cancelAndClearTouchTargets(ev);
    resetTouchState();
}

当收到 ACTION_DOWN(新手势的开始),清空之前所有的 TouchTarget 并重置触摸状态。这确保每次新的手势都从干净的状态开始。resetTouchState() 还会清除 FLAG_DISALLOW_INTERCEPT 标志。

阶段二:检查是否拦截(第 2660-2678 行)
java 复制代码
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    if (!disallowIntercept || isBackGestureInProgress) {
        intercepted = onInterceptTouchEvent(ev);
        ev.setAction(action); // 恢复 action,防止在 onInterceptTouchEvent 中被修改
    } else {
        intercepted = false;
    }
} else {
    intercepted = true;
}

关键逻辑:

  • 只有在 ACTION_DOWN 或者已有子 View 在处理事件(mFirstTouchTarget != null)时,才调用 onInterceptTouchEvent()
  • 如果子 View 调用了 requestDisallowInterceptTouchEvent(true) 设置了 FLAG_DISALLOW_INTERCEPT,则跳过拦截检查(除非有返回手势正在进行)。
  • 如果没有 TouchTarget(即之前没有子 View 消费 DOWN 事件),并且当前事件不是 DOWN,直接设置 intercepted = true,事件由当前 ViewGroup 自己处理。
阶段三:安全策略检查(第 2647 行)
java 复制代码
if (onFilterTouchEventForSecurity(ev)) {
    // ... 正常分发逻辑
}

onFilterTouchEventForSecurity() 检查事件是否被遮挡(obscured),如果窗口设置了 filterTouchesWhenObscured 且事件来自被遮挡的窗口,则过滤该事件。

阶段四:子 View 的遍历和命中测试(第 2705-2796 行)

仅在 ACTION_DOWNACTION_POINTER_DOWN(且启用了 split)、或 ACTION_HOVER_MOVE 时执行:

java 复制代码
if (actionMasked == MotionEvent.ACTION_DOWN
        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {

遍历流程:

  1. 通过 buildTouchDispatchChildList() 获取按 Z-order 排序的子 View 列表。
  2. 从前到后i = childrenCount - 1 向下遍历)扫描子 View。
  3. 对每个子 View 进行命中测试 (hit test):
    • child.canReceivePointerEvents():检查 View 是否可见且无动画。
    • isTransformedTouchPointInView(x, y, child, null):将触摸坐标转换到子 View 的局部坐标系,检查是否在其范围内。
  4. 如果命中,调用 dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign) 将事件分发给该子 View。
  5. 如果子 View 消费了事件(返回 true),则调用 addTouchTarget(child, idBitsToAssign) 将其添加到 TouchTarget 链表头部。
阶段五:TouchTarget 链表的构建(第 2975-2980 行)
java 复制代码
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
    final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
    target.next = mFirstTouchTarget;
    mFirstTouchTarget = target;
    return target;
}

TouchTarget 以单链表形式组织,新的 target 插入到链表头部。每个 TouchTarget 持有对子 View 的引用和对应的 pointer ID 位掩码。

阶段六:事件的实际分发(第 2799-2835 行)
java 复制代码
if (mFirstTouchTarget == null) {
    // 没有任何子 View 消费事件,当作普通 View 处理
    handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
} else {
    // 遍历 TouchTarget 链表进行分发
    TouchTarget predecessor = null;
    TouchTarget target = mFirstTouchTarget;
    while (target != null) {
        final TouchTarget next = target.next;
        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
            handled = true;  // DOWN 阶段已经分发过了
        } else {
            final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
            if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
                handled = true;
            }
            if (cancelChild) {
                // 如果需要取消,从链表中移除该 target
                if (predecessor == null) {
                    mFirstTouchTarget = next;
                } else {
                    predecessor.next = next;
                }
                target.recycle();
                target = next;
                continue;
            }
        }
        predecessor = target;
        target = next;
    }
}

关键点:

  • 如果 mFirstTouchTarget == null(没有子 View 消费事件),调用 dispatchTransformedTouchEvent(ev, canceled, null, ...),其中 child = null 表示调用 super.dispatchTouchEvent(),即当作普通 View 处理。
  • 如果被拦截(intercepted = true),之前的 TouchTarget 会收到 ACTION_CANCEL 事件,然后从链表中移除。
阶段七:ACTION_UP/ACTION_CANCEL 时的清理(第 2837-2846 行)
java 复制代码
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);
}
  • ACTION_UP/ACTION_CANCEL:完全重置触摸状态。
  • ACTION_POINTER_UP:仅移除对应 pointer 的 ID 位。

3.4 View.dispatchTouchEvent() 的流程

源码位置:frameworks/base/core/java/android/view/View.java 第 16391-16431 行

java 复制代码
public boolean dispatchTouchEvent(MotionEvent event) {
    if (event.isTargetAccessibilityFocus()) {
        if (!isAccessibilityFocusedViewOrHost()) {
            return false;
        }
        event.setTargetAccessibilityFocus(false);
    }
    boolean result = false;
    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        stopNestedScroll();  // 新手势开始时停止嵌套滚动
    }
    if (onFilterTouchEventForSecurity(event)) {
        result = performOnTouchCallback(event);
    }
    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }
    return result;
}

核心逻辑在 performOnTouchCallback() 中(第 16437-16465 行):

java 复制代码
private boolean performOnTouchCallback(MotionEvent event) {
    boolean handled = false;
    if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
        handled = true;
    }
    ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED) {
        handled = li.mOnTouchListener.onTouch(this, event);
    }
    if (handled) {
        return true;
    }
    return onTouchEvent(event);
}

优先级顺序

  1. 滚动条拖拽处理
  2. OnTouchListener.onTouch():如果设置了 OnTouchListener 且 View 是 ENABLED 的,先调用 listener
  3. 如果 listener 返回 true,不再 调用 onTouchEvent()
  4. 否则调用 onTouchEvent() 处理

这个优先级设计意味着:OnTouchListener 的优先级高于 onTouchEvent(),开发者可以通过设置 listener 来拦截事件而不需要继承 View。

3.5 View.onTouchEvent() 的流程

源码位置:frameworks/base/core/java/android/view/View.java 第 17898-18097 行

这是 View 对触摸事件的默认处理逻辑,涵盖了点击、长按等手势的识别。

首先检查 clickable 状态(第 17904-17906 行):

java 复制代码
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
        || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
        || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

DISABLED 状态处理(第 17908-17917 行):

java 复制代码
if ((viewFlags & ENABLED_MASK) == DISABLED
        && (mPrivateFlags4 & PFLAG4_ALLOW_CLICK_WHEN_DISABLED) == 0) {
    // ...
    return clickable;  // disabled 但 clickable 的 View 仍然消费事件,只是不响应
}

关键设计 :即使 View 是 DISABLED 的,只要它是 clickable 的,就仍然消费(consume)触摸事件。这意味着事件不会穿透到下面的 View。

各事件类型的处理

  • ACTION_DOWN(第 17991-18032 行):

    • 设置 mHasPerformedLongPress = false
    • 如果在滚动容器中,设置 PFLAG_PREPRESSED 标志并延迟 tapTimeout 毫秒后显示按下状态
    • 如果不在滚动容器中,立即设置按下状态,并启动长按检测计时器
  • ACTION_MOVE(第 18046-18097 行):

    • 更新 drawable 热点位置
    • 如果手指移出 View 边界(考虑 touchSlop),取消长按检测和按下状态
  • ACTION_UP(第 17926-17989 行):

    • 如果不是长按,执行点击操作:performClickInternal()
    • 通过 post(mPerformClick) 异步执行点击,让 View 的视觉状态先更新
    • 清除按下状态
  • ACTION_CANCEL(第 18034-18044 行):

    • 清除按下状态
    • 移除所有回调

3.6 ViewGroup.dispatchTouchEvent 流程图















未命中


命中














dispatchTouchEvent 开始
onFilterTouchEventForSecurity

安全检查通过?
返回 false
actionMasked ==

ACTION_DOWN?
cancelAndClearTouchTargets

resetTouchState

清空所有 TouchTarget
ACTION_DOWN 或

mFirstTouchTarget != null?
FLAG_DISALLOW_INTERCEPT

已设置?
intercepted = true

没有 target 也不是 DOWN

自己处理
intercepted = false

子 View 禁止拦截
调用 onInterceptTouchEvent
onInterceptTouchEvent

返回 true?
intercepted = true
intercepted = false
未取消 且

未拦截?
ACTION_DOWN 或

ACTION_POINTER_DOWN?
遍历子 View

从前到后扫描
子 View 命中测试

canReceivePointerEvents

isTransformedTouchPointInView
还有下一个

子 View?
dispatchTransformedTouchEvent

分发给命中的子 View
子 View

消费了事件?
addTouchTarget

加入 TouchTarget 链表

alreadyDispatched = true
mFirstTouchTarget

!= null?
dispatchTransformedTouchEvent

child = null

调用 super.dispatchTouchEvent

当作普通 View 处理
遍历 TouchTarget 链表
是刚分发过的

newTouchTarget?
handled = true

跳过重复分发
intercepted 为 true?
发送 ACTION_CANCEL

给该 target

从链表移除
dispatchTransformedTouchEvent

分发给 target.child
还有下一个 target?
ACTION_UP 或

ACTION_CANCEL?
resetTouchState

清空所有状态
ACTION_POINTER_UP

且 split 模式?
removePointersFromTouchTargets

移除对应 pointer
返回 handled

3.7 完整触摸事件时序图

View (子View/Button) ViewGroup (ContentView) PhoneWindow Activity DecorView ViewRootImpl (InputStage管道) WindowInputEventReceiver View (子View/Button) ViewGroup (ContentView) PhoneWindow Activity DecorView ViewRootImpl (InputStage管道) WindowInputEventReceiver === ACTION_DOWN 事件 === dispatchPointerEvent ->> dispatchTouchEvent onUserInteraction() super.dispatchTouchEvent = ViewGroup.dispatchTouchEvent ViewGroup.dispatchTouchEvent View.dispatchTouchEvent addTouchTarget(V, idBits) mFirstTouchTarget = V === ACTION_MOVE 事件 === mFirstTouchTarget != null 直接分发给已有target 不重新遍历子View 直接发给已有target === ACTION_UP 事件 === performClick() 触发点击 resetTouchState() 清空TouchTarget resetTouchState() onInputEvent(event) enqueueInputEvent() InputStage管道处理 (NativePreIme->>...->>ViewPostIme) mView.dispatchPointerEvent(DOWN) Window.Callback.dispatchTouchEvent(DOWN) getWindow().superDispatchTouchEvent(DOWN) mDecor.superDispatchTouchEvent(DOWN) cancelAndClearTouchTargets() resetTouchState() onInterceptTouchEvent(DOWN) ->> false dispatchTransformedTouchEvent(DOWN, child=VG) onInterceptTouchEvent(DOWN) ->> false dispatchTransformedTouchEvent(DOWN, child=V) OnTouchListener.onTouch(DOWN)? onTouchEvent(DOWN) return true (消费事件) return true return true (View树消费了) return true onInputEvent(MOVE) dispatchPointerEvent(MOVE) dispatchTouchEvent(MOVE) superDispatchTouchEvent(MOVE) superDispatchTouchEvent(MOVE) onInterceptTouchEvent(MOVE) ->> false dispatchTransformedTouchEvent(MOVE, child=VG) onInterceptTouchEvent(MOVE) ->> false dispatchTransformedTouchEvent(MOVE, child=V) onTouchEvent(MOVE) return true return true return true return true onInputEvent(UP) dispatchPointerEvent(UP) dispatchTouchEvent(UP) superDispatchTouchEvent(UP) superDispatchTouchEvent(UP) onInterceptTouchEvent(UP) ->> false dispatchTransformedTouchEvent(UP, child=VG) onInterceptTouchEvent(UP) ->> false dispatchTransformedTouchEvent(UP, child=V) onTouchEvent(UP) return true return true return true return true


4. 事件冲突的解决方案

4.1 外部拦截法

核心思想 :由父 ViewGroup 决定是否拦截事件。重写父 ViewGroup 的 onInterceptTouchEvent() 方法。

实现模板

java 复制代码
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    boolean intercepted = false;
    switch (ev.getActionMasked()) {
        case MotionEvent.ACTION_DOWN:
            intercepted = false;  // DOWN 事件不能拦截,否则子 View 收不到任何事件
            break;
        case MotionEvent.ACTION_MOVE:
            if (/* 满足父容器的拦截条件,如水平滑动距离 > 垂直滑动距离 */) {
                intercepted = true;
            }
            break;
        case MotionEvent.ACTION_UP:
            intercepted = false;  // UP 事件一般不拦截
            break;
    }
    return intercepted;
}

原理 :在 ViewGroup.dispatchTouchEvent() 第 2663-2678 行,onInterceptTouchEvent() 在每个 MOVE 事件到来时都会被调用(只要 mFirstTouchTarget != null)。一旦返回 true,当前 TouchTarget 的子 View 会收到 ACTION_CANCEL,后续事件直接由父 ViewGroup 处理。

4.2 内部拦截法

核心思想 :由子 View 决定父 ViewGroup 是否可以拦截事件。子 View 通过调用 requestDisallowInterceptTouchEvent() 来控制父容器的拦截行为。

实现模板(子 View):

java 复制代码
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    switch (ev.getActionMasked()) {
        case MotionEvent.ACTION_DOWN:
            // DOWN 时请求父容器不要拦截
            getParent().requestDisallowInterceptTouchEvent(true);
            break;
        case MotionEvent.ACTION_MOVE:
            if (/* 不需要自己处理,应该让父容器处理 */) {
                // 允许父容器拦截
                getParent().requestDisallowInterceptTouchEvent(false);
            }
            break;
    }
    return super.dispatchTouchEvent(ev);
}

配合要求 :父 ViewGroup 的 onInterceptTouchEvent()ACTION_DOWN 时必须返回 false(这是默认行为),并在非 DOWN 事件时返回 true:

java 复制代码
// 父 ViewGroup
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
        return false;  // DOWN 不拦截
    }
    return true;  // 其他事件默认拦截
}

4.3 FLAG_DISALLOW_INTERCEPT 的工作原理

源码位置:frameworks/base/core/java/android/view/ViewGroup.java 第 393 行, 第 3244-3261 行

java 复制代码
// 第 393 行
protected static final int FLAG_DISALLOW_INTERCEPT = 0x80000;

// 第 3244-3261 行
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
        return;  // 状态未变,直接返回
    }
    if (disallowIntercept) {
        mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
    } else {
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    }
    // 向上传递给父容器
    if (mParent != null) {
        mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
    }
}

关键特性:

  1. 该标志会向上递归传播到所有父级 ViewGroup。
  2. ACTION_DOWN 时,resetTouchState()清除 该标志(第 2901 行)。因此 FLAG_DISALLOW_INTERCEPTACTION_DOWN 无效。
  3. dispatchTouchEvent() 第 2664-2673 行,如果该标志被设置,则跳过 onInterceptTouchEvent() 调用。

4.4 嵌套滑动机制(NestedScrolling)简介

FLAG_DISALLOW_INTERCEPT 虽然能解决简单的冲突,但对于嵌套滑动场景(如 CoordinatorLayout + RecyclerView)还不够灵活。Android 5.0 引入了 NestedScrolling 机制:

  • NestedScrollingChild :子 View 实现该接口,在滑动前通过 dispatchNestedPreScroll() 询问父容器是否要先消费一部分滑动距离。
  • NestedScrollingParent :父容器实现该接口,在 onNestedPreScroll() 中决定要消费多少滑动距离。

这种机制的优势在于:父子 View 可以协同消费同一个滑动事件,而不是简单的二选一。

在 View.java 中,ACTION_DOWN 时调用 stopNestedScroll() 来结束之前的嵌套滑动(第 16410 行)。

4.5 常见冲突场景及解决方案

冲突场景 方向冲突 推荐方案 说明
ScrollView 嵌套 RecyclerView 同方向(垂直+垂直) 内部拦截法或使用 NestedScrollView NestedScrollView 实现了 NestedScrollingParent,天然支持嵌套
ViewPager 嵌套 ListView 交叉方向(水平+垂直) 外部拦截法 ViewPager 已内置处理,在水平滑动时拦截事件
ViewPager2 嵌套 ViewPager2 同方向(水平+水平) 内部拦截法 子 ViewPager2 在自身可滑动时禁止父容器拦截
RecyclerView 嵌套 RecyclerView 同方向 NestedScrolling RecyclerView 同时实现了 Parent 和 Child 接口
DrawerLayout + 内容区域滑动 水平冲突 DrawerLayout 内置处理 边缘滑动触发抽屉,中间区域滑动传递给内容

4.6 外部拦截法与内部拦截法对比流程图

内部拦截法


ACTION_DOWN
子View调用

requestDisallowIntercept

true
父ViewGroup

跳过onInterceptTouchEvent
ACTION_MOVE
子View是否

需要自己处理?
保持禁止拦截

自己处理事件
调用requestDisallow

InterceptTouchEvent false

允许父容器拦截
下次MOVE

父ViewGroup

onInterceptTouchEvent

返回true拦截事件
外部拦截法


ACTION_DOWN
父ViewGroup

onInterceptTouchEvent

返回 false
事件传递给子View
ACTION_MOVE
父ViewGroup

onInterceptTouchEvent

需要拦截?
返回 true

子View收到CANCEL

后续事件由父处理
返回 false

事件继续传给子View


5. 关键源码解析

5.1 ViewRootImpl.WindowInputEventReceiver

文件:frameworks/base/core/java/android/view/ViewRootImpl.java 第 10434-10463 行

java 复制代码
// ViewRootImpl.java 第 10434-10463 行
final class WindowInputEventReceiver extends InputEventReceiver {
    public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
        super(inputChannel, looper);
    }

    @Override
    public void onInputEvent(InputEvent event) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventForCompatibility");
        List<InputEvent> processedEvents;
        try {
            processedEvents =
                mInputCompatProcessor.processInputEventForCompatibility(event);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        if (processedEvents != null) {
            if (processedEvents.isEmpty()) {
                finishInputEvent(event, true);
            } else {
                for (int i = 0; i < processedEvents.size(); i++) {
                    enqueueInputEvent(
                            processedEvents.get(i), this,
                            QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY, true);
                }
            }
        } else {
            enqueueInputEvent(event, this, 0, true);  // 正常路径
        }
    }
    // ...
}

注释说明

  • WindowInputEventReceiver 继承 InputEventReceiver,是应用进程接收输入事件的入口。
  • onInputEvent() 由 native 层通过 JNI 回调。
  • mInputCompatProcessor 处理兼容性转换(如旧版本 API 的事件格式差异)。
  • 最终通过 enqueueInputEvent() 将事件放入处理队列。

5.2 ViewRootImpl.ViewPostImeInputStage.processPointerEvent

文件:frameworks/base/core/java/android/view/ViewRootImpl.java 第 8011-8060 行

java 复制代码
// ViewRootImpl.java 第 8011-8031 行(ViewPostImeInputStage 内部)
private int processPointerEvent(QueuedInputEvent q) {
    final MotionEvent event = (MotionEvent)q.mEvent;
    final int action = event.getAction();
    if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
        mIsPressedGesture = false;
    }
    boolean handled = false;
    if (!disableHandwritingInitiatorForIme()
            || mWindowAttributes.type != TYPE_INPUT_METHOD) {
        handled = mHandwritingInitiator.onTouchEvent(event);
    }
    // ...
    mAttachInfo.mHandlingPointerEvent = true;
    // 关键调用:将事件分发给 View 树的根节点(DecorView)
    handled = handled || mView.dispatchPointerEvent(event);
    // ...
    mAttachInfo.mHandlingPointerEvent = false;
    // ...
}

注释说明

  • mViewDecorView,是 View 树的根节点。
  • dispatchPointerEvent() 是事件进入 View 树的桥梁方法。
  • mHandwritingInitiator 是 Android 14+ 的手写输入检测器,优先处理手写识别。

5.3 View.dispatchPointerEvent

文件:frameworks/base/core/java/android/view/View.java 第 16741-16747 行

java 复制代码
// View.java 第 16741-16747 行
public final boolean dispatchPointerEvent(MotionEvent event) {
    if (event.isTouchEvent()) {
        return dispatchTouchEvent(event);   // 触摸事件走这里
    } else {
        return dispatchGenericMotionEvent(event);  // 鼠标悬停等走这里
    }
}

注释说明

  • 这是一个 final 方法,不可被重写。
  • 根据事件类型分流:触摸事件调用 dispatchTouchEvent(),其他指针事件调用 dispatchGenericMotionEvent()

5.4 DecorView.dispatchTouchEvent

文件:frameworks/base/core/java/com/android/internal/policy/DecorView.java 第 475-479 行

java 复制代码
// DecorView.java 第 475-479 行
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    final Window.Callback cb = mWindow.getCallback();
    return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
            ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}

注释说明

  • mWindow.getCallback() 返回的就是 Activity(Activity 实现了 Window.Callback 接口)。
  • 如果 Callback 存在、窗口未销毁、且是主窗口(mFeatureId < 0),则将事件发送给 Activity.dispatchTouchEvent()
  • 否则直接调用 super.dispatchTouchEvent()(即 ViewGroup.dispatchTouchEvent())。

5.5 Activity.dispatchTouchEvent

文件:frameworks/base/core/java/android/app/Activity.java 第 4535-4543 行

java 复制代码
// Activity.java 第 4535-4543 行
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();  // 通知有用户交互
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;  // View 树消费了事件
    }
    return onTouchEvent(ev);  // 所有 View 都不消费,Activity 最后处理
}

注释说明

  • onUserInteraction() 在每个手势的 DOWN 事件时调用,可用于重置屏幕超时。
  • getWindow().superDispatchTouchEvent() 将事件回传给 View 树(PhoneWindow -> DecorView -> ViewGroup)。
  • 如果 View 树不消费,Activity.onTouchEvent() 是最后的处理机会。

5.6 PhoneWindow.superDispatchTouchEvent

文件:frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java 第 1980-1982 行

java 复制代码
// PhoneWindow.java 第 1980-1982 行
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

文件:frameworks/base/core/java/com/android/internal/policy/DecorView.java 第 519-521 行

java 复制代码
// DecorView.java 第 519-521 行
public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);  // 调用 ViewGroup.dispatchTouchEvent
}

注释说明

  • PhoneWindow 简单地将调用委托给 DecorView。
  • DecorView 的 superDispatchTouchEvent() 调用 super.dispatchTouchEvent(),即跳过 DecorView 自己的 dispatchTouchEvent()(避免再次回到 Activity),直接调用 ViewGroup.dispatchTouchEvent() 开始在 View 树中递归分发。

5.7 View.dispatchTouchEvent

文件:frameworks/base/core/java/android/view/View.java 第 16391-16431 行

java 复制代码
// View.java 第 16391-16431 行
public boolean dispatchTouchEvent(MotionEvent event) {
    // 无障碍焦点处理
    if (event.isTargetAccessibilityFocus()) {
        if (!isAccessibilityFocusedViewOrHost()) {
            return false;
        }
        event.setTargetAccessibilityFocus(false);
    }
    boolean result = false;

    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        stopNestedScroll();  // 新手势开始,停止嵌套滚动
    }

    if (onFilterTouchEventForSecurity(event)) {
        result = performOnTouchCallback(event);  // 核心处理逻辑
    }

    // 手势结束或 DOWN 未被消费时,停止嵌套滚动
    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }
    return result;
}

5.8 View.performOnTouchCallback

文件:frameworks/base/core/java/android/view/View.java 第 16437-16465 行

java 复制代码
// View.java 第 16437-16465 行
private boolean performOnTouchCallback(MotionEvent event) {
    boolean handled = false;
    // 1. 滚动条拖拽处理
    if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
        handled = true;
    }
    // 2. OnTouchListener 优先处理
    ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED) {
        handled = li.mOnTouchListener.onTouch(this, event);
    }
    if (handled) {
        return true;  // listener 消费了,不再调用 onTouchEvent
    }
    // 3. 调用 onTouchEvent
    return onTouchEvent(event);
}

注释说明

  • 优先级:OnTouchListener.onTouch() > onTouchEvent()
  • 只有 View 是 ENABLED 状态时才会调用 OnTouchListener
  • 如果 OnTouchListener 返回 true,onTouchEvent() 不会被调用

5.9 ViewGroup.dispatchTouchEvent

文件:frameworks/base/core/java/android/view/ViewGroup.java 第 2635-2853 行

(由于此方法代码量较大,这里列出核心结构,完整代码见第 3.3 节的逐段分析)

java 复制代码
// ViewGroup.java 第 2635-2853 行(简化结构)
public boolean dispatchTouchEvent(MotionEvent ev) {
    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
        // 阶段1:ACTION_DOWN 时清空状态
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }

        // 阶段2:拦截检查
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                intercepted = onInterceptTouchEvent(ev);
            } else {
                intercepted = false;
            }
        } else {
            intercepted = true;
        }

        // 阶段3:遍历子 View(仅 DOWN/POINTER_DOWN)
        if (!canceled && !intercepted) {
            if (actionMasked == MotionEvent.ACTION_DOWN || ...) {
                // 从前到后扫描子 View,命中测试,分发事件
                // 消费则 addTouchTarget
            }
        }

        // 阶段4:分发给 TouchTarget 或自己
        if (mFirstTouchTarget == null) {
            handled = dispatchTransformedTouchEvent(ev, canceled, null, ALL_POINTER_IDS);
        } else {
            // 遍历 TouchTarget 链表分发
        }

        // 阶段5:清理
        if (canceled || actionMasked == MotionEvent.ACTION_UP) {
            resetTouchState();
        }
    }
    return handled;
}

5.10 ViewGroup.dispatchTransformedTouchEvent

文件:frameworks/base/core/java/android/view/ViewGroup.java 第 3094-3164 行

java 复制代码
// ViewGroup.java 第 3094-3164 行
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
    final int oldAction = event.getAction();
    try {
        final boolean handled;
        if (cancel) {
            event.setAction(MotionEvent.ACTION_CANCEL);  // 强制设为 CANCEL
        }

        // 计算要传递的 pointer
        final int oldPointerIdBits = event.getPointerIdBits();
        int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

        if (newPointerIdBits == 0) {
            if (event.getAction() != MotionEvent.ACTION_CANCEL) {
                return false;  // 非 CANCEL 且无匹配 pointer,丢弃
            } else {
                newPointerIdBits = oldPointerIdBits;  // CANCEL 保留所有 pointer
            }
        }

        final MotionEvent transformedEvent;
        if (newPointerIdBits == oldPointerIdBits) {
            // pointer 数量相同,可以复用或简单偏移
            if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);  // 自己处理
                } else {
                    // 坐标偏移后分发给子 View
                    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);  // 多点触控分离
        }

        // 应用矩阵变换后分发
        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);
        }

        transformedEvent.recycle();
        return handled;
    } finally {
        event.setAction(oldAction);  // 恢复原始 action
    }
}

注释说明

  • child == null 时调用 super.dispatchTouchEvent(),即 View.dispatchTouchEvent(),将 ViewGroup 当作普通 View 处理。
  • 坐标变换:先偏移(offsetLocation),如果有矩阵变换(如旋转、缩放),还需要应用逆矩阵。
  • 多点触控时,通过 event.split(newPointerIdBits) 将事件拆分为只包含特定 pointer 的子事件。

5.11 ViewGroup.onInterceptTouchEvent

文件:frameworks/base/core/java/android/view/ViewGroup.java 第 3300-3308 行

java 复制代码
// ViewGroup.java 第 3300-3308 行
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;
}

注释说明

  • 默认实现几乎不拦截任何事件。
  • 唯一例外:鼠标左键点击滚动条时拦截(为了支持鼠标拖拽滚动条)。
  • 自定义 ViewGroup 需要重写此方法来实现拦截逻辑。

5.12 View.onTouchEvent

文件:frameworks/base/core/java/android/view/View.java 第 17898-18097 行

java 复制代码
// View.java 第 17898-18097 行(关键片段)
public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;
    final int action = event.getAction();

    // clickable 判断:CLICKABLE || LONG_CLICKABLE || CONTEXT_CLICKABLE
    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

    // DISABLED 状态:clickable 的 View 仍消费事件
    if ((viewFlags & ENABLED_MASK) == DISABLED
            && (mPrivateFlags4 & PFLAG4_ALLOW_CLICK_WHEN_DISABLED) == 0) {
        if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        return clickable;
    }

    // TouchDelegate 处理
    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }

    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                // ... 执行点击:performClickInternal()
                break;
            case MotionEvent.ACTION_DOWN:
                // ... 设置按下状态,启动长按检测
                break;
            case MotionEvent.ACTION_CANCEL:
                // ... 清除所有状态
                break;
            case MotionEvent.ACTION_MOVE:
                // ... 检查是否移出边界,更新热点
                break;
        }
        return true;  // clickable 的 View 总是消费事件
    }
    return false;
}

5.13 TouchTarget 内部类

文件:frameworks/base/core/java/android/view/ViewGroup.java 第 8964-9023 行

java 复制代码
// ViewGroup.java 第 8964-9023 行
private static final class TouchTarget {
    private static final int MAX_RECYCLED = 32;
    private static final Object sRecycleLock = new Object[0];
    private static TouchTarget sRecycleBin;
    private static int sRecycledCount;

    public static final int ALL_POINTER_IDS = -1; // 所有位都为1

    public View child;           // 被触摸的子 View
    public int pointerIdBits;    // 该 target 处理的 pointer ID 位掩码
    public TouchTarget next;     // 链表的下一个节点

    public static TouchTarget obtain(@NonNull View child, int pointerIdBits) {
        // 从对象池获取或新建
        final TouchTarget target;
        synchronized (sRecycleLock) {
            if (sRecycleBin == null) {
                target = new TouchTarget();
            } else {
                target = sRecycleBin;
                sRecycleBin = target.next;
                sRecycledCount--;
                target.next = null;
            }
        }
        target.child = child;
        target.pointerIdBits = pointerIdBits;
        return target;
    }

    public void recycle() {
        // 回收到对象池
        synchronized (sRecycleLock) {
            if (sRecycledCount < MAX_RECYCLED) {
                next = sRecycleBin;
                sRecycleBin = this;
                sRecycledCount += 1;
            }
            child = null;
        }
    }
}

注释说明

  • TouchTarget 使用对象池模式(Object Pool),避免频繁 GC。最多缓存 32 个对象。
  • pointerIdBits 是位掩码,每一位对应一个 pointer ID。ALL_POINTER_IDS = -1 表示所有 pointer。
  • 链表结构通过 next 字段实现,mFirstTouchTarget 是链表头。

5.14 ViewGroup.requestDisallowInterceptTouchEvent

文件:frameworks/base/core/java/android/view/ViewGroup.java 第 3244-3261 行

java 复制代码
// ViewGroup.java 第 3244-3261 行
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    // 优化:状态未变时直接返回
    if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
        return;
    }

    if (disallowIntercept) {
        mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
    } else {
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    }

    // 递归向上传递
    if (mParent != null) {
        mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
    }
}

6. 多点触控的事件分发

6.1 MotionEvent 中 Pointer 的概念

在 Android 中,每一个触摸点(手指)被称为一个 Pointer。MotionEvent 可以同时包含多个 Pointer 的信息。

关键概念:

  • Pointer Index:Pointer 在当前 MotionEvent 中的索引(0, 1, 2, ...),每次事件可能变化。
  • Pointer ID :Pointer 的唯一标识符,在整个手势过程中保持不变。通过 event.getPointerId(index) 获取。
  • Action IndexACTION_POINTER_DOWN/ACTION_POINTER_UP 事件中,发生变化的 Pointer 的索引。通过 event.getActionIndex() 获取。

源码位置:frameworks/base/core/java/android/view/MotionEvent.java

复制代码
ACTION_POINTER_DOWN = 5  (第 278 行)
ACTION_POINTER_UP   = 6  (第 289 行)

事件编码方式:action = ACTION_POINTER_DOWN | (pointerIndex << ACTION_POINTER_INDEX_SHIFT)

6.2 ACTION_POINTER_DOWN/UP 的分发逻辑

ViewGroup.dispatchTouchEvent() 中,多点触控的关键处理在第 2705-2710 行:

java 复制代码
// ViewGroup.java 第 2705-2710 行
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;

分发规则

  1. 启用 split 模式FLAG_SPLIT_MOTION_EVENTS,Android 3.0+ 默认启用)时:

    • ACTION_DOWN:第一个手指按下,进行正常的子 View 遍历和命中测试。
    • ACTION_POINTER_DOWN:新手指按下时,再次进行子 View 遍历和命中测试。这意味着新手指可能命中不同的子 View。
    • 每个 Pointer 的 ID 被记录在 TouchTarget.pointerIdBits 中。
  2. 不启用 split 模式时:

    • idBitsToAssign = TouchTarget.ALL_POINTER_IDS,所有 pointer 都分配给同一个 TouchTarget。
    • 所有手指的事件都发送给同一个子 View。
  3. ACTION_POINTER_UP 的处理(第 2842-2846 行):

    java 复制代码
    } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
        final int actionIndex = ev.getActionIndex();
        final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
        removePointersFromTouchTargets(idBitsToRemove);
    }

    当一个非最后的手指抬起时,从 TouchTarget 中移除对应的 pointer ID 位。如果某个 TouchTarget 的所有 pointer 都被移除,该 target 也会被移除。

6.3 多个 TouchTarget 对应不同子 View

在 split 模式下,一个 ViewGroup 可以同时拥有多个 TouchTarget,每个 target 对应不同的子 View 和不同的 pointer 集合。

示例场景

假设一个 LinearLayout 中有两个 Button(ButtonA 和 ButtonB):

  1. 第一个手指(pointer 0)按在 ButtonA 上:

    • ACTION_DOWN 触发
    • 命中 ButtonA,创建 TouchTarget(child=ButtonA, pointerIdBits=0b01)
    • mFirstTouchTarget -> {ButtonA, 0b01}
  2. 第二个手指(pointer 1)按在 ButtonB 上:

    • ACTION_POINTER_DOWN 触发
    • 命中 ButtonB,创建 TouchTarget(child=ButtonB, pointerIdBits=0b10)
    • mFirstTouchTarget -> {ButtonB, 0b10} -> {ButtonA, 0b01}
  3. 后续的 ACTION_MOVE 事件:

    • 遍历 TouchTarget 链表
    • 对 ButtonA 的 target:通过 event.split(0b01) 提取只包含 pointer 0 的事件,分发给 ButtonA
    • 对 ButtonB 的 target:通过 event.split(0b10) 提取只包含 pointer 1 的事件,分发给 ButtonB
  4. 第二个手指抬起(pointer 1):

    • ACTION_POINTER_UP 触发
    • removePointersFromTouchTargets(0b10) 移除 pointer 1
    • ButtonB 的 target 被移除
    • mFirstTouchTarget -> {ButtonA, 0b01}
  5. 如果新手指按在已有 target 的 View 上(第 2750-2756 行):

    java 复制代码
    newTouchTarget = getTouchTarget(child);
    if (newTouchTarget != null) {
        // 子 View 已经在接收触摸
        // 将新 pointer 追加到已有 target
        newTouchTarget.pointerIdBits |= idBitsToAssign;
        break;
    }

dispatchTransformedTouchEvent 中的 split 处理(第 3139-3141 行):

java 复制代码
if (newPointerIdBits == oldPointerIdBits) {
    // pointer 数量相同,直接转发
    // ...
} else {
    transformedEvent = event.split(newPointerIdBits);  // 拆分事件
}

event.split() 方法会创建一个新的 MotionEvent,只包含指定 pointer ID 的触摸数据。这确保每个子 View 只收到属于自己的 pointer 信息。


附录:关键源码文件索引

文件 路径 关键行号
View.java frameworks/base/core/java/android/view/View.java dispatchTouchEvent: 16391, performOnTouchCallback: 16437, onTouchEvent: 17898, dispatchPointerEvent: 16741
ViewGroup.java frameworks/base/core/java/android/view/ViewGroup.java dispatchTouchEvent: 2635, onInterceptTouchEvent: 3300, dispatchTransformedTouchEvent: 3094, requestDisallowInterceptTouchEvent: 3244, TouchTarget: 8964, addTouchTarget: 2975, resetTouchState: 2898, cancelAndClearTouchTargets: 2935
ViewRootImpl.java frameworks/base/core/java/android/view/ViewRootImpl.java InputStage管道: 1742, InputStage基类: 7151, ViewPostImeInputStage: 7779, processPointerEvent: 8011, WindowInputEventReceiver: 10434, enqueueInputEvent: 10203
Activity.java frameworks/base/core/java/android/app/Activity.java dispatchTouchEvent: 4535, onTouchEvent: 4285
DecorView.java frameworks/base/core/java/com/android/internal/policy/DecorView.java dispatchTouchEvent: 475, superDispatchTouchEvent: 519, onInterceptTouchEvent: 546
PhoneWindow.java frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java superDispatchTouchEvent: 1980
Window.java frameworks/base/core/java/android/view/Window.java Callback接口: 354, superDispatchTouchEvent(抽象): 该文件中声明
InputEventReceiver.java frameworks/base/core/java/android/view/InputEventReceiver.java 基类定义: 38
MotionEvent.java frameworks/base/core/java/android/view/MotionEvent.java ACTION常量: 219-289
相关推荐
zh_xuan2 小时前
Android compose 使用viewModel
android·compose
0pen12 小时前
我用 AI 写了一个 Android 群控工具,从零到可用只花了一个下午
android·人工智能
雾江流2 小时前
LSPosed 2.0.0 | 强大的安卓Root框架,支持XP模块
android·软件工程
谢白羽11 小时前
vllm实践
android·vllm
电子云与长程纠缠12 小时前
Godot学习03 - 实例化、层级访问、Export
android·学习·godot
毕设源码-朱学姐12 小时前
【开题答辩全过程】以 基于Android的便民系统的设计与实现为例,包含答辩的问题和答案
android
鬼蛟12 小时前
Spring————事务
android·java·spring
qq_1702647514 小时前
unity出安卓年龄分级的arr包问题
android·unity·游戏引擎
kejiashao15 小时前
Android View的绘制流程及事件分发机制
android