
一、核心概念1.1 三个关键方法方法作用存在于dispatchTouchEvent(MotionEvent)分发事件决定事件交给谁处理View ViewGrouponInterceptTouchEvent(MotionEvent)拦截事件决定是否把事件交给子 View仅 ViewGrouponTouchEvent(MotionEvent)消费事件真正处理触摸事件View ViewGroup1.2 事件类型事件说明ACTION_DOWN手指按下新事件序列的开始ACTION_MOVE手指滑动ACTION_UP手指抬起ACTION_CANCEL事件被中断/拦截需恢复状态ACTION_POINTER_DOWN多指按下ACTION_POINTER_UP多指抬起核心原则一个触控点的事件序列从 DOWN 到 UP/CANCEL只能给一个 View 消费。二、ViewGroup 事件分发流程ViewGroup 的dispatchTouchEvent分为三大步骤┌─────────────────────────────────────────────────────────┐ │ 1. 拦截判断 (onInterceptTouchEvent) │ │ 2. 寻找消费 DOWN 的子 View (TouchTarget) │ │ 3. 派发事件 (dispatchTransformedTouchEvent) │ └─────────────────────────────────────────────────────────┘2.1 拦截判断java// 只有 DOWN 事件或已有子 View 消费了 DOWN 时才会判断是否拦截 if (actionMasked ACTION_DOWN || mFirstTouchTarget ! null) { // 子 View 可通过 requestDisallowInterceptTouchEvent(true) 禁止父 View 拦截 final boolean disallowIntercept (mGroupFlags FLAG_DISALLOW_INTERCEPT) ! 0; if (!disallowIntercept) { intercepted onInterceptTouchEvent(ev); // 默认返回 false } else { intercepted false; } } else { // 没有子 View 消费 DOWN后续事件直接自己处理 intercepted true; }关键点onInterceptTouchEvent默认返回false不拦截子 View 调用requestDisallowInterceptTouchEvent(true)可禁止父 View 拦截通过设置FLAG_DISALLOW_INTERCEPT2.2 寻找消费 DOWN 的子 View对于每个ACTION_DOWN或ACTION_POINTER_DOWNViewGroup 会逆序遍历子 Viewz-index 高的优先寻找合适的子 Viewjava// 遍历子 View从后往前显示在上层的优先 for (int i childrenCount - 1; i 0; i--) { // 检查1) 子 View 能否接收事件 2) 触摸点是否在子 View 范围内 if (!child.canReceivePointerEvents() || !isTransformedTouchPointInView(x, y, child, null)) { continue; } // 分发 DOWN 事件给子 View if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // 子 View 消费了 DOWN → 创建 TouchTarget 绑定 newTouchTarget addTouchTarget(child, idBitsToAssign); break; } }2.3 TouchTarget 机制TouchTarget 是 ViewGroup 用来记录哪个子 View 消费了哪个触控点的数据结构它是一个链表头节点是mFirstTouchTarget每个节点记录子 View该 View 绑定的触控点 ID用位运算存储触控点 ID一个 int 的 32 个位每位代表一个触控点 IDmFirstTouchTarget → TouchTarget(viewButtonA, pointerIdBits0001) ↓ TouchTarget(viewButtonB, pointerIdBits0010)2.4 派发事件javaif (mFirstTouchTarget null) { // 没有子 View 消费 DOWN → 自己处理调用 super.dispatchTouchEvent handled dispatchTransformedTouchEvent(ev, canceled, null, ALL_POINTER_IDS); } else { // 遍历 TouchTarget 链表把事件分发给对应的子 View while (target ! null) { if (alreadyDispatchedToNewTouchTarget target newTouchTarget) { handled true; // DOWN 事件已在上面分发过了 } else { // 正常分发 MOVE/UP 等事件 final boolean cancelChild resetCancelNextUpFlag(target.child) || intercepted; if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled true; } // 如果被拦截发送 CANCEL 并移除 TouchTarget if (cancelChild) { // 移除该 target... } } target target.next; } }三、View 事件处理流程View 的dispatchTouchEvent逻辑相对简单javapublic boolean dispatchTouchEvent(MotionEvent event) { // 1. 安全检查是否被非全屏窗口遮挡 if (!onFilterTouchEventForSecurity(event)) return false; // 2. 先调用 OnTouchListener优先级高于 onTouchEvent if (mOnTouchListener ! null mOnTouchListener.onTouch(this, event)) { return true; // OnTouchListener 消费了事件 } // 3. 调用 onTouchEvent 处理 return onTouchEvent(event); }3.1 onTouchEvent 核心逻辑onTouchEvent负责处理单击、长按等事件核心机制是延时任务Handler事件处理逻辑ACTION_DOWN设置按下状态发送延时任务检测长按默认 400msACTION_MOVE检测手指是否移出 View 范围移出则取消按下状态ACTION_UP如果未触发长按 → 触发onClick取消按下状态ACTION_CANCEL取消所有状态移除延时任务单击 vs 长按的判断DOWN 时启动一个CheckForLongPress延时任务如果在超时前收到 UP → 取消长按任务触发onClick如果超时后仍未收到 UP → 触发onLongClick设置mHasPerformedLongPress trueUP 时若mHasPerformedLongPress true则不再触发onClick四、完整事件分发流程图Activity.dispatchTouchEvent() ↓ DecorView.dispatchTouchEvent() ↓ ViewGroup.dispatchTouchEvent() ┌─────────────────────────┐ │ 1. 安全检查 (onFilter...)│ │ 2. 拦截判断 │ │ - DOWN / 有target → onInterceptTouchEvent() │ - 否则 → intercepted true │ 3. 寻找消费DOWN的子View │ │ - 逆序遍历子View │ │ - 找到则创建TouchTarget│ │ 4. 派发事件 │ │ - 无target → 自己处理 │ │ - 有target → 分发给子View│ └─────────────────────────┘ ↓ 子 View.dispatchTouchEvent() ↓ View.onTouchEvent() / OnTouchListener ↓ 返回 true (消费) / false (不消费向上回传)五、高频Q1事件分发的三个方法是什么作用分别是什么答dispatchTouchEvent分发事件是入口方法onInterceptTouchEvent拦截事件仅 ViewGroup 有决定是否把事件交给子 ViewonTouchEvent消费事件真正处理触摸逻辑Q2onTouch 和 onTouchEvent 有什么区别onClick 又是在哪里触发的答onTouch是OnTouchListener接口的方法优先级高于onTouchEvent如果onTouch返回true则onTouchEvent不会执行onClick在onTouchEvent的ACTION_UP中触发通过PerformClickRunnable优先级onTouch onTouchEvent onClickQ3子 View 如何阻止父 View 拦截事件答子 View 在dispatchTouchEvent中调用getParent().requestDisallowInterceptTouchEvent(true)这会设置父 View 的FLAG_DISALLOW_INTERCEPT标志使父 View 的onInterceptTouchEvent失效注意对ACTION_DOWN无效因为 DOWN 事件会重置所有状态resetTouchState()Q4一个事件序列被拦截后会发生什么答ViewGroup 拦截某个事件如 MOVE后会给之前消费 DOWN 的子 View 发送一个ACTION_CANCEL子 View 收到 CANCEL 后应恢复状态如停止动画、重置按下状态等后续该触控点的事件序列由 ViewGroup 自己处理Q5为什么事件序列只能给一个 View 处理答这是 Android 的设计原则避免多个 View 同时响应同一触控点造成混乱ViewGroup 通过TouchTarget机制实现消费 DOWN 时创建 TouchTarget 绑定后续事件直接分发给该 View例外情况ViewGroup 拦截事件或出现 CANCEL 导致事件序列中断Q6View 的onTouchEvent默认返回什么什么情况下会返回 true答默认情况下如果 View 是CLICKABLE或LONG_CLICKABLEonTouchEvent返回true消费事件即使 View 是DISABLED状态只要设置了clickable仍然会返回true只是不执行点击回调Q7自定义 ViewGroup 时如何处理事件分发答重写onInterceptTouchEvent控制拦截时机通常逻辑ACTION_DOWN不拦截让子 View 有机会处理ACTION_MOVE根据业务判断是否拦截如滑动冲突时判断滑动方向ACTION_UP不拦截拦截后要在onTouchEvent中处理自己的逻辑Q8滑动冲突怎么解决答 常见三种方案方案说明外部拦截法父 View 重写onInterceptTouchEvent在 MOVE 时根据条件拦截内部拦截法子 View 在dispatchTouchEvent中调用requestDisallowInterceptTouchEvent控制父 View嵌套滚动机制使用NestedScrollingParent/NestedScrollingChild推荐如 RecyclerView AppBarLayoutQ9多点触控时事件如何分发答每个触控点有独立的Pointer IDACTION_POINTER_DOWN/ACTION_POINTER_UP表示新手指按下/抬起ViewGroup 通过TouchTarget的pointerIdBits位图记录每个子 View 绑定了哪些触控点事件分离FLAG_SPLIT_MOTION_EVENTS开启时每个子 View 只收到自己感兴趣的触控点事件Q10如果所有 View 都不消费 DOWN 事件后续事件会怎样答DOWN 事件从 Activity → DecorView → 根 ViewGroup 一路分发如果所有 View 都不消费都返回 falseDOWN 事件最终回传到 Activity 的onTouchEvent后续 MOVE、UP 事件不会再进入 View 树直接由 Activity 处理六、总结速记┌────────────────────────────────────────────────────────────┐ │ 分发入口dispatchTouchEvent() │ │ 拦截控制onInterceptTouchEvent()ViewGroup 专属 │ │ 消费处理onTouchEvent() │ │ 监听优先OnTouchListener onTouchEvent onClick │ │ 核心原则一个触控点序列只给一个 View │ │ 拦截后果子 View 收到 CANCEL需恢复状态 │ │ 禁止拦截requestDisallowInterceptTouchEvent(true) │ │ 多点处理TouchTarget pointerIdBits 位图绑定 │ └────────────────────────────────────────────────────────────┘