整理自模拟面试,覆盖触摸反馈 / 事件机制 / 事件分发 / ViewRootImpl / requestDisallowInterceptTouchEvent / 滑动冲突
一句话:触摸反馈就是用户手指按下后,界面立即给出的视觉、状态或触觉响应,用来告诉用户“这次操作已经被接收了”。
pressed 按压态ripple 波纹效果performHapticFeedback() 触觉反馈ACTION_DOWN 就出现DOWN / MOVE / UP / CANCEL,避免按压态残留一句话:Android 触摸事件本质是 MotionEvent,核心就是事件从上到下分发,再由某一层消费。
ACTION_DOWN:手指按下ACTION_MOVE:手指移动ACTION_UP:手指抬起ACTION_CANCEL:事件被中断或被父容器接管dispatchTouchEvent():负责分发事件onInterceptTouchEvent():负责决定父容器是否拦截,只有 ViewGroup 有onTouchEvent():负责真正消费事件dispatchTouchEvent():传onInterceptTouchEvent():截onTouchEvent():处理Activity
-> Window
-> DecorView
-> ViewGroup
-> 子 View
InputDispatcher
-> ViewRootImpl
-> DecorView
-> ViewGroup
-> 子 View
MotionEventActivity.dispatchTouchEvent()Window.superDispatchTouchEvent() 传给 DecorViewViewGroup.dispatchTouchEvent()onTouchEvent() 中消费ViewGroup.dispatchTouchEvent() 的核心逻辑onInterceptTouchEvent()ACTION_DOWN 很重要一句话:ACTION_DOWN 是一整组触摸事件的起点,谁接住了 DOWN,后续事件通常就优先发给谁。
DOWN 的作用MOVE / UP 建立上下文ACTION_DOWN 会怎样MOVE / UPACTION_DOWNDOWN 就拦截,子 View 拿不到事件起点DOWN 先放行,MOVE 再判断是否接管MOVE 阶段拦截是怎么发生的一句话:父容器通常是在 onInterceptTouchEvent() 里,根据滑动方向、距离和边界,在 MOVE 阶段动态决定是否接管事件。
DOWN:父容器返回 false,先不拦截MOVE:父容器再次收到事件onInterceptTouchEvent() 里判断是否应该接管true,子 View 收到 ACTION_CANCELonTouchEvent()touchSlopOnTouchListener 和 OnClickListener 的关系结论:OnTouchListener 不会天然让点击失效,关键看 onTouch() 的返回值。
onTouch() 返回 true:事件被提前消费,OnClickListener 通常不会触发onTouch() 返回 false:事件继续走 onTouchEvent(),点击仍然可能生效View.dispatchTouchEvent() 里会优先执行 OnTouchListeneronTouchEvent() 中通过 performClick() 触发的onTouchEvent() 也能点击View 基类默认实现onTouchEvent() 已经处理了按压态和 performClick()OnClickListener 后,View 通常会进入可点击逻辑ViewRootImpl 和事件分发是什么关系一句话:ViewRootImpl 不负责父子 View 之间的拦截决策,但它负责把系统输入事件接进当前窗口的根 View。
DecorViewmeasure / layout / draw 调度ViewRootImpl 接收输入ViewRootImpl 再把事件派发给 DecorViewViewGroup / View 分发流程ViewGroup.dispatchTouchEvent()ViewRootImpl一句话:滑动冲突本质上是父子控件都想消费同一组事件,核心是尽早明确“这次手势到底归谁处理”。
ScrollView 嵌套 RecyclerViewRecyclerView 嵌套 ViewPageronInterceptTouchEvent() 中决定是否拦截requestDisallowInterceptTouchEvent() 控制父容器先别拦截touchSloptouchSlopcanScrollHorizontally() / canScrollVertically()touchSlop + 边界判断requestDisallowInterceptTouchEvent() 的作用和调用时机一句话:子 View 用它通知父容器“当前这组事件后续先别拦截我”。
ACTION_DOWNrequestDisallowInterceptTouchEvent(true)告诉父容器先不要太早接管
ACTION_MOVE
truefalserequestDisallowInterceptTouchEvent结论:父 ViewGroup 是在 dispatchTouchEvent() 过程中判断这个标记的。
requestDisallowInterceptTouchEvent(true) 后FLAG_DISALLOW_INTERCEPTonInterceptTouchEvent()if (disallowIntercept) {
intercepted = false
} else {
intercepted = onInterceptTouchEvent(ev)
}
ACTION_DOWN 时这个标记会失效或被重置一句话:因为 ACTION_DOWN 代表新一轮事件序列开始,父容器会重置上一次触摸分发状态。
FLAG_DISALLOW_INTERCEPTrequestDisallowInterceptTouchEvent(true) 保护的是“当前这组事件”| 知识点 | 一句话速记 |
|---|---|
| 触摸反馈 | 用户按下后立即给出的视觉或触觉响应,本质是提升操作确定性 |
| 事件机制 | dispatchTouchEvent 负责传,onInterceptTouchEvent 负责截,onTouchEvent 负责处理 |
ACTION_DOWN |
事件序列起点,谁消费了 DOWN,后续事件通常就归谁 |
MOVE 拦截 |
父容器通常在 onInterceptTouchEvent() 里动态判断是否接管 |
| 点击失效 | 常见原因是 DOWN 没消费、父容器拦截、OnTouchListener 提前吃掉事件 |
ViewRootImpl |
负责把输入送进 View 树,也负责布局和绘制调度 |
| 滑动冲突 | 核心是根据方向、距离、边界尽早明确事件归属 |
requestDisallowInterceptTouchEvent |
子 View 用来告诉父容器“这组事件后续先别拦截我” |