基本概述
需要关注下是View还是ViewGroup,但整体上是一致的。
事件分发机制主要解决的核心问题是,将Touch事件,传递 到 对应的view 来 消费:
- 如何传递
- 传递给谁
我们学习事件分发机制解决什么问题:
- 如何利用机制去拦截目标事件
- 如何利用机制去处理事件冲突
整体原则
- 传递时,从外向内、从父到子
- 消费时,从内向外、从子到父
三座大山
整个机制三个关键函数:
- dispatchTouchEvent, 事件传递
- 返回true,表明事件已经消费,会终止事件向下(由父向子)传递,结束
- 返回false,表明事件未被消费,将会回传给父View的onTouchEvent
- 调用super.dispatchTouchEvent,继续下传
- onInterceptTouchEvent
- 这个方法只存在于 ViewGroup 中
- 返回true,表明要拦截事件,会回调onTouchEvent
- 返回false,表明不做拦截,后面会继续下传
- onTouchEvent, 事件消费
- 返回true,表明事件被消费,结束
- 返回false,表明事件没有被消费,将会继续回传父view
- 调用super.onTouchEvent,继续往上回传
事件分发路径图
在默认的实现下,事件流向是一个U型图。

需要注意的是,
关键点
一般情况下,有以下事件:
- ACTION_DOWN
- ACTION_MOVE
- ACTION_UP
- ACTION_CANCEL
KEY 1
而我们说的一个事件就是指上面的某一个,但若说一个完整事件序列,则是从down--move--...--cancel/up这样一个所有一个操作周期所发生的所有事件。
KEY 2
不同的事件的分发路径其实是有所差别的。
一旦前面的事件确定了接收的view,那么分发的U形图就会"向上收缩"。 例如下图被ViewGroup2接收后,后续事件在默认情况下,如红线只会走到VG2然后被处理,蓝色区域不会走了。

KEY 3
CANCEL事件会稍微特殊一点,发生在后续事件被其他view拦截的时候,原来接收事件的view就会收到CANCEL事件。
例如,Down事件被下层消费之后,后续的Move、Up事件是有可能会被上层拦截的 。这也正是常常发生事件冲突的地方。
若被拦截,则原本应该消费的View会收到cancel事件。
滑动冲突的处理方式
滑动冲突是非常常见的,通常发生在多个可滑动的布局嵌套的场景,例如RecyclerView{ ScrollView }, RecyclerView{ ListView }, ViewPager{ RecyclerView }
典型的,根据两个控件的滑动方向,可以将滑动冲突分成两类:一个是不同方向的滑动冲突 ,如外层控件垂直滑动,内层控件水平滑动。另一个就是同方向的滑动冲突,如内外两层控件都是垂直滑动。
拦截方法有两种,本质也是万变不离其宗(改变分发的路线):
- 外部拦截法
- 内部拦截法
在一些特殊业务场景下,可能需要内外一起完成配合来实现特殊的效果。
(1)外部拦截法
在外层ViewGroup中的onInterceptTouchEvent中,对move事件进行精细化控制。
例如在外部是纵向、内部是横向的滚动场景时,我们判断dy>dx时【实际条件根据业务场景设置】选择拦截即在onInterceptTouchEvent中return true.
(2)内部拦截法
在内部ViewGroup中的dispatchTouchEvent或onTouchEvent方法中,调用api requestDisallowInterceptTouchEvent(true)
来阻止父布局拦截后续事件。
这时候内部ViewGroup是不会回调onInterceptTouchEvent的,所以需要在dispatchTouchEvent或onTouchEvent方法中调用。
例如在外部是纵向、内部是横向的滚动场景时,我们判断dx>dy时【实际条件根据业务场景设置】
设置requestDisallowInterceptTouchEvent(true)
做滑动布局,应该在哪拦截事件?
首先滑动布局是ViewGroup类型,里面存在子View。
1)明确一点,滑动事件肯定是在ViewGroup层去拦截;
2)滑动事件拦截,不应该以Down为起点,而应该是MOVE。否则:子View的点击事件也会被拦截
3)Move事件应该在onInterceptTouchEvent中拦截