自定义View事件的分发及传递机制

一丶  一个完整的事件传递机制  必定包含三种事件: 由一个 down 事件、 多 个 move 事件,一个 up 事件组成(还有一个cancel事件,该事件是在被上层拦截时触发)。

手指落下(ACTION_DOWN) -> 移动(ACTION_MOVE) -> 离开(ACTION_UP)  

二丶Touch一般的传递流程:Activity------>window(唯一实现类是PhoneWindow)------>顶级View(DecorView)------>ViewGroup------>View    ;(可概括为一句话:责任链模式,事件层层传递,直到被消费)



三丶监听Touch事件的两种方式:setTouchListener和直接重写三个方法:dispatchToucEventonInterceptTouchEvent,  onTouchEvent.


1.setTouchListener:

此方法监听的优先级比较高,如果在onTouchListener的onTouch方法里面执行了return true,那么说明消费了该事件 ,而OnTouchEvent是接收不到该事件的,因此在onClickListener里面的Onclick方法是执行不到的,因为Onclick是在OnTouchEvent种被调用的,因此Touch事件走不到OnTouchEvent事件的话,Onclick方法是不会被执行的.

2,dispatchToucEvent:

为什么ViewGroupdispatchToucEvent,View 也有dispatchToucEvent,是因为View 可以注册很多事件监听器,例如:单击事件(onClick)、长按事件(onLongClick)、触摸事件(onTouch),并且View自身也有 onTouchEvent 方法,那么问题来了,这么多与事件相关的方法应该由谁管理?就是dispatchTouchEvent.()

那么,这么多的事件,他们的事件调度顺序是怎样的呢?

从源码的角度去分析: 

从设计的角度去分析:

1).单击事件(onClickListener) 需要两个两个事件(ACTION_DOWN 和 ACTION_UP )才能触发,如果先分配给onClick判断,等它判断完,用户手指已经离开屏幕了,定然造成 View 无法响应其他事件,应该最后调用。(最后)

2).长按事件(onLongClickListener) 同理,也是需要长时间等待才能出结果,肯定不能排到前面,但因为不需要ACTION_UP,应该排在 onClick 前面。(onLongClickListener > onClickListener)

3).触摸事件(onTouchListener) 如果用户注册了触摸事件,说明用户要自己处理触摸事件了,这个应该排在最前面。(最前)

4).View自身处理(onTouchEvent) 提供了一种默认的处理方式,如果用户已经处理好了,也就不需要了,所以应该排在 onTouchListener 后面。(onTouchListener > onTouchEvent)


由上可以推导出来:

事件的调度顺序应该是onTouchListener > onTouchEvent > onLongClickListener > onClickListener


举个栗子:


LinearLayout这里设置了点击一个事件,然后在执行的时候你会发现怎么点击都不会接收到消息,这是因为View这里设置了clickble的属性为true.也就是这个事件被孩子吃掉了.那么父亲是不会再走TouchEvent的

3.onInterceptTouchEvent:

这个是ViewGroup专有的,该事件在ViewGroup一层一层传递的,最终传递给 ViewViewGroup 要比它的 ChildView 先拿到事件,并且有权决定是否告诉要告诉 ChildView

一般情况下,该事件在ViewGroup中是这样分发的:

1.判断自身是否需要(询问 onInterceptTouchEvent 是否拦截),如果需要,调用自己的 onTouchEvent。

2.自身不需要或者不确定,则询问 ChildView ,一般来说是调用手指触摸位置的 ChildView。

3.如果子 ChildView 不需要则调用自身的 onTouchEvent。

ViewGroup通过遍历ChildView,确定手指点在哪个ChildView的区域内,然后将事件发放到该ChildView,而当ChildView存在覆盖的情况时,ViewGroup会将事件分发到最上层的ChildView上(一般后加载的CHildView是会覆盖前面加载了的,所以最上层的是最后加载的)



当手指点击有重叠区域时,分如下几种情况:

只有 View1 可点击时,事件将会分配给 View1,即使被 View2 遮挡,这一部分仍是 View1 的可点击区域。

只有 View2 可点击时,事件将会分配给 View2。

View1 和 View2 均可点击时,事件会分配给后加载的 View2,View2 将事件消费掉,View1接收不到事件。

注意:

上面说的是可点击,可点击包括很多种情况,只要你给View注册了onClickListener、onLongClickListener、OnContextClickListener其中的任何一个监听器或者设置了android:clickable=”true”就代表这个 View 是可点击的。

另外,某些 View 默认就是可点击的,例如,Button,CheckBox 等。

给 View 注册 OnTouchListener 不会影响 View 的可点击状态。即使给 View 注册 OnTouchListener ,只要不返回 true 就不会消费事件

3. ViewGroup 和 ChildView 同时注册了事件监听器(onClick等),哪个会执行?

事件优先给 ChildView,会被 ChildView消费掉,ViewGroup 不会响应。

4. 所有事件都应该被同一 View 消费

在上面的例子中我们分析后可以了解到,同一次点击事件只能被一个 View 消费,主要是为了防止事件响应混乱,如果再一次完整的事件中分别将不同的事件分配给了不同的 View 容易造成事件响应混乱。

View 中 onClick 事件需要同时接收到 ACTION_DOWN 和 ACTION_UP 才能触发,如果分配给了不同的 View,那么 onClick 将无法被正确触发。

安卓为了保证所有的事件都是被一个 View 消费的,对第一次的事件( ACTION_DOWN )进行了特殊判断,View 只有消费了 ACTION_DOWN 事件,才能接收到后续的事件(可点击控件会默认消费所有事件),并且会将后续所有事件传递过来,不会再传递给其他 View,除非上层 View 进行了拦截。

如果上层 View 拦截了当前正在处理的事件,会收到一个 ACTION_CANCEL,表示当前事件已经结束,后续事件不会再传递过来。(详细细节可以查看源码~~~~)

核心要点(重点!!!!)

事件分发原理: 责任链模式,事件层层传递,直到被消费。

View 的dispatchTouchEvent主要用于调度自身的监听器和 onTouchEvent。

View的事件的调度顺序是 onTouchListener > onTouchEvent > onLongClickListener > onClickListener 。

不论 View 自身是否注册点击事件,只要 View 是可点击的就会消费事件。

事件是否被消费由返回值决定,true 表示消费,false 表示不消费,与是否使用了事件无关。

ViewGroup 中可能有多个 ChildView 时,将事件分配给包含点击位置的 ChildView。

ViewGroup 和 ChildView 同时注册了事件监听器(onClick等),由 ChildView 消费。

一次触摸流程中产生事件应被同一 View 消费,全部接收或者全部拒绝。

只要接受 ACTION_DOWN 就意味着接受所有的事件,拒绝 ACTION_DOWN 则不会收到后续内容。

如果当前正在处理的事件被上层 View 拦截,会收到一个 ACTION_CANCEL,后续事件不会再传递过来


最后附一张示意图:

推荐阅读更多精彩内容