外观
触摸事件怎样分发?
标准回答
触摸在应用内部的主线是:Activity 的 dispatchTouchEvent 进入窗口与 DecorView,再沿 ViewGroup 的 dispatchTouchEvent 向下寻找处理者,最终由目标 View 处理。dispatchTouchEvent 是“这次事件怎么交给合适处理者”的入口,不等于一定由当前 View 消费。
ViewGroup 可以在 onInterceptTouchEvent 判断是否拦截。如果不拦截,就继续交给合适的子 View;如果拦截,改由容器自己的处理路径接管。孩子已经接收 DOWN 后,父容器中途拦截时,孩子通常收到 ACTION_CANCEL,表示原来的触摸处理应结束。
对普通 enabled View,注册的 OnTouchListener.onTouch 通常先于 onTouchEvent 获得机会。onTouch 返回 true 表示消费,onTouchEvent 通常不再处理这次事件;未消费才继续交给 onTouchEvent。能否接收后续事件还受 DOWN 是否消费、父容器拦截和取消等条件影响。
onClick 是控件根据有效点击序列产生的语义动作,通常由 onTouchEvent 的处理通过 performClick 触发,不是每收到一个触摸事件就执行。自定义控件若自己消费触摸并识别点击,应在确认点击时调用 performClick,并实现相应语义,让点击监听与可访问性机制正常工作。
关键机制
- dispatchTouchEvent 是分发入口;onInterceptTouchEvent 属于 ViewGroup,用于决定是否把后续事件交给自己处理。
- OnTouchListener 返回 true 表示消费,通常不会再走该 View 的 onTouchEvent,因此点击可能被吃掉。
- 子 View 接收 DOWN 后,父容器若中途拦截,子 View 会收到 CANCEL;滑动冲突可以按方向、距离阈值和嵌套滚动协议协调。
通俗例子
横向轮播嵌在纵向列表里:DOWN 先记录位置,MOVE 超过 touchSlop 后判断主方向,让对应组件处理;不要一开始就把所有事件拦截。
追问与回答
为什么 onTouch 有日志,onClick 却不执行?
先看 onTouch 是否返回 true。如果它把事件消费了,默认 onTouchEvent 的点击识别可能没有机会完成。也要检查是否发生移动超出点击范围、父容器拦截或 ACTION_CANCEL。onClick 需要满足点击条件,并不是 onTouch 之后无条件执行的下一步。
onInterceptTouchEvent 返回 true,就是消费事件了吗?
它表达的是父容器要拦截、改变事件的分发去向,不等同于业务已经处理成功。随后仍要看容器自己的 onTouchEvent 等处理路径是否消费。把拦截和消费分清,才能解释为什么某个孩子收不到事件,以及父容器接管后是否真正响应。
易混淆点
某个 View 没消费 DOWN,通常不会成为这一整段手势的触摸目标。自定义点击应调用 performClick,兼顾无障碍。
参考资料
内容来源
- Android 技术札记 · 原条目
touch