技术背景
从 View 体系中认识 Touch 事件传递,暂时留一条线索:
" View 最原始的事件从哪里来? ”
从 WindowCallbacKWrapper开始的。
那么,我们开始吧!
tip:阅读源码前,建议读懂 Android View体系之基础常识及技巧。
千里之行,始于Activity
从 window 层开始下发事件后, Activity 开始处理事件,会调用 ViewGroup#dispatchTouchEvent  
| 1 | Activity.java | 
Activity#dispatchTouchEvent 方法最后会通过 DecorView 触发 ViewGroup#dispatchTouchEvent 开始分发事件。
总结:Activity 下发 Touch 事件到 DecorView 并由 DecorView 开始向下传递。
ViewGroup之核心分发
DecorView 调用 dispatchTouchEvent 分发 Touch 事件。代码很长,可是不难,逻辑比较清晰。
| 1 | 
 | 
如果看完上述注释还有点蒙,一定要多撸几次源码。有几个点想说明一下,可能大家会好理解要一点。
- TouchTarget mFirstTouchTarget的作用- mFirstTouchTarget贯穿- dispatchTouchEvent流程,实际上它是一个链表,用于记录所有接受- Touch事件的 子- view。在日常开发中有没有遇到过这样一个逻辑“ 如果一个- view没有接收过- ACTION_DOWN的事件,那么后续- ACTION_MOVE和- ACTION_UP也一定不会分发到这个- view。 ”。这个逻辑是基于- mFirstTouchTarget记录的- view实现的。
总结: 根据上述注释逻辑链。
- 过滤’不合法’的 Touch事件;
- 如果是 MotionEvent.ACTION_DOWN ,则初始化一些状态;
- 判断事件是否需要拦截,是否需要取消;
- 如果不需要拦截&不是取消事件,则会向子 view下发Touch事件;
- 如果没有任何子 view消费事件,则会自己处理,如果已有子view消费事件,判断当前新处理的target对象是否是mFirstTouchTarget链表最新一个,如果是则默认为当前传递已经传递事件,否则返回子view递归结果。
上述有两个递归,在 注释10 和 注释14,这里你可能会有疑惑,这两个的关系是什么。注释10 实际上是返回以 viewGroup 为根节点的 view 下是否有节点消费点击事件,如果有则记录下当前子 view。注释14 实际上是返回以 viewGroup 为根节点的 view 下是否有节点消费事件。  
ViewGroup之递归入口
在上一章节,dispatchTouchEvent 多次调用 dispatchTransformedTouchEvent,这里做下简单分析。
| 1 | private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, | 
总结:dispatchTransformedTouchEvent 会对非 MotionEvent.ACTION_CANCEL 事件做转化并递归返回所有事件的下发结果。
View也可分发事件?
既然不是 view,那么 dispatchTouchEvent 应该不是属于下发范畴的,那会是什么呢?
| 1 | public boolean dispatchTouchEvent(MotionEvent event) { | 
上述逻辑表明有三种场景下会返回 true 结果。
两种场景为:第一种是拖拽场景,比如listview等控件存在这种逻辑;另一种是开发者设置了 OnTouchListener 对象并在 onTouch 函数中处理并返回 true 结果。
最后一种场景为普遍场景,及如果没有上述两种场景且是当前是最外层 view 时(事件已经无法再传递),则会调用自身的 onTouch 方法处理。
总结:view#dispatchTouchEvent 会在事件下发链末端调用,并把当前 view 的 onTouch 返回值作为 dispatchTouchEvent 返回值。
回看ViewGroup如何拦截
上述篇章的下发逻辑都需要判断 Touch 事件是否需要被拦截,先看看代码。
| 1 | public boolean onInterceptTouchEvent(MotionEvent ev) { | 
上面的代码在绝大部分情况下都返回 false 。除非你鼠标事件且在上面滚动,这个场景很像你在滚动网页一样,那么当前的页面就会拦截滚动事件进行页面滚动。
值得注意的是:如果你在该方法返回 true 进行拦截,那么你会走下面的调用逻辑:
- viewGroup#dispatchTouchEvent
- viewGroup#dispatchTransformedTouchEvent
- view#dispatchTouchEvent
- view#onTouchEvent
总结:viewGroup#onInterceptTouchEvent 是 ViewGroup 特有的方法。默认情况下 ViewGroup 不会拦截 Touch 事件,如果拦截了 Touch 事件,则会交给 View#onTouch 进行处理。
事件的宿命onTouchEvent
这个方法是处理 Touch 事件,并返回结果给 dispatchTouchEvent 的。可以理解为:它决定了某个 view 是否真正消费 Touch 事件。直接看源码。
| 1 | public boolean onTouchEvent(MotionEvent event) { | 
上述代码中,有两处 callback 的逻辑你可能还没有完全明白,一个是 TapCallback,一个是 LongPressCallback,分别对应 mPendingCheckForTap 和 mPendingCheckForLongPress,看下完整的代码。
| 1 | // 代码段1,类 CheckForTap | 
代码段1 会调用 代码段5,实际上也是延迟发送 “处理长按行为”。和直接调用 代码5 不同,代码5 中延迟 500-delayOffset ms 执行 “处理长按行为”,而源码的调用基本都是默认 delayOffset = 0 。代码1 则以 delayOffset = 100 先延迟 100 ms之后延迟 400 ms 发送处理长按行为,同样需要 500 ms 才会支持 “处理长按行为”。那到底为啥要这么做呢?
原因是当前处理的 view 位于可滑动的容器内需要延迟处理接收的按压事件。这样讲有点抽象,你可以这样理解,android 把 MotionEvent.ACTION_DOWN 场景区分为 滑动(scroll) 和 轻敲(tap),用延迟的时间来判断手势已经发生了位移。如果发生了位移,则还依然需要保持判断有效长按时间(500 ms)不变,所以会追加 400 ms延迟来 post 一个 “处理长按行为” 任务。
上述6个代码段用于加深理解 onTouchEvent 内事件的处理而已。从上上段代码上看,我们总结下整个流程: 
- 如果 view不可用则根据是否可点击来直接消费MotionEvent.ACTION_UP
- 如果 view设置了mTouchDelegate,则默认消费 Touch 事件
- 如果 view可点击或者在 tooltip 显示状态下默认消费事件,否则返回 false 给dispatchTouchEvent。- MotionEvent.ACTION_UP分支会设置按压状态,触发点击或长按事件,最后重置状态
- MotionEvent.ACTION_DOWN分支延迟发送“处理长按行为”
- MotionEvent.ACTION_CANCEL分支重置处理 Touch 过程中设置的状态
- MotionEvent.ACTION_MOVE分支处理滑动 RippleDrawable 效果并在手势滑出 View 范围情况下重置状态
 
总结: onTouchEvent 是真正完成对 Touch 事件的处理,并把处理结果作为dispatchTouchEvent 的递归结果。
5个案例加强理解
GitHub链接上有本次 Touch传递测试代码
测试案例两个 viewGroup 和 一个 view

- 场景一:View3#onTouchEvent返回true消费所有事件,上层不拦截。

场景一可知:View3 消费所有事件并返回 true,对于上层下发的任何事件,dispatchTouchEvent 都返回 true。
- 场景二:View3#onTouchEvent返回false不消费 Touch 事件,上层不拦截,Linearlayout2返回true消费所有 Touch 事件。

场景二可知:如果末层不消费所有事件,则 ACTION_DOWN 会开始从末层向上传递。Linearlayout2 消费了ACTION_DOWN之后,其及上层dispatchTouchEvent 都返回 true。ACTION_DOWN 之后的事件序列(如ACTION_MOVE,ACTION_UP)都会往Linearlayout2分发,其下层就再也收不到后续事件了。
- 场景三:Linearlayout2#onInterceptTouchEvent返回true拦截 Touch 事件,但是Linearlayout2#onTouchEvent返回false不消费事件。

场景三可知:Linearlayout2 拦截了 ACTION_DOWN 之后,其子 View 再也收不到任何事件,其消费结果由 onTouchEvent 决定,如果不消费,则往上层传,直到找到某层消费事件。如果没有任何一层消费,则后续事件序列也不会下发了。
- 场景四:Linearlayout2#onInterceptTouchEvent返回true拦截 Touch 事件,但是Linearlayout2#onTouchEvent返回true消费事件。

场景四可知:Linearlayout2 拦截了 ACTION_DOWN 之后,其子 View 再也收不到任何事件,如果消费 ACTION_DOWN,则后续事件序列都往Linearlayout2 下发。
- 场景五:View3#onTouchEvent返回true消费所有除ACTION_CANCEL事件,但是Linearlayout2#onInterceptTouchEvent拦截了ACTION_MOVE事件且不消费任何事件。

场景五可知:ACTION_DOWN传递到 View3被其消费,后续序列事件本应该传递到 View3。当 ACTION_MOVE 被 Linearlayout2拦截之后,无论是否消费,View3再也收不到 ACTION_MOVE 及其后续的事件序列,但是会在事件被一次拦截时收到 ACTION_CANCEL,是否消费 ACTION_CANCEL 的结果会被当做此次传递的结果返回。此后,此次ACTION_MOVE后续的事件序列往 Linearlayout2 下发。
2张流程图看懂没?

