Android事件分发机制
Android事件分发机制
为什么会有事件分发机制
android上面的view是树形结构的,view可能会重叠在一起,当我们点击的地方有过个view都可以响应的时候,这个点击事件应该交给谁来处理,就需要事件分发机制。
1.概述
事件分发的三个重要方法
public boolean dispatchTouchEvent(MotionEvent event)
public boolean onInterceptTouchEvent(MotionEvent event)
public boolean onTouchEvent(MotionEvent event)
MotionEvent取值如下:
public static final int ACTION_DOWN = 0;
public static final int ACTION_UP = 1;
public static final int ACTION_MOVE = 2;
public static final int ACTION_CANCEL = 3;
ACTION_DOWN 表示手指按下时发出的消息
ACTION_UP 表示手指抬起时发出的消息
ACTION_MOVE 表示手指移动时发出的消息
ACTION_CANCEL 表示结束事件时手指发出的消息
举个例子讲解事件分发机制:
布局为Activity嵌套两个ViewGroup,最顶层的ViewGroup里面套一个TextView
如下不拦截消息时,ACTION_DOWN消息的完整传递过程为:
注意:
对于Activity来说不需要 onInterceptTouchEvent 方法,因为拦截没有意义,拦截后整个view树都点击不了。
对于view来说,事件要么消费,要么回传,也没有拦截的必要。
因此在Activity,ViewGroup,TextView中都有disPatchTouchEvent方法和onTouchEvent方法。
对于ViewGroup来说,多了一个onInterceptTouchEvent方法。
先说不包含 onInterceptTouchEvent 方法时消息传递过程,再来看包含onInterceptTouchEvent方法时消息传递过程
2. 不包含 onInterceptTouchEvent 方法时 ACTION_DOWN 消息传递过程
什么是拦截?看下面这三个方法都有一个boolean类型的返回值。默认返回false表示没有拦截,即自己不做处理。返回true时,表示自己处理这个事件,就是拦截了。
public boolean dispatchTouchEvent(MotionEvent event)
public boolean onInterceptTouchEvent(MotionEvent event)
public boolean onTouchEvent(MotionEvent event)
2.1 dispatchTouchEvent 返回值简介
dispatchTouchEvent 方法对于返回值的处理比较特殊,默认的处理方法并不是返回false,而是直接调用super.dispatchTouchEvent.这种处理方式与直接返回false不同。因此对于dispatchTouchEvent,有三种返回值:
- 默认的super.dispatchTouchEvent
- 返回true
- 返回false
2.2 在 dispatchTouchEvent 方法中返回 true 拦截消息
2.2.1 在 Activity 的 dispatchTouchEvent 方法中拦截消息
在 Activity 的 dispatchTouchEvent 方法中拦截消息后,消息会直接断掉,不会往任何地方传递,只有 Activity 的 dispatchTouchEvent 方法收到 ACTION_DOWN 消息。
2.2.2 在 ViewGroup 的 dispatchTouchEvent 方法中拦截消息
在 ViewGroup 的 dispatchTouchEvent 方法中拦截消息后,ACTION_DOWN 消息同样会直接停止传递,其他任何子控件都收不到消息。
2.2.2 在 View 的 dispatchTouchEvent 方法中拦截消息
在传递到 View 的 dispatchTouchEvent 方法后,消息就停止传递了。
因此,无论在哪个控件的 dispatchTouchEvent 方法中拦截消息,消息都会直接停止传递,后面的子控件都不会接收到这个消息。
2.3 在 dispatchTouchEvent 方法中返回 false 拦截消息
先说结论:在 dispatchTouchEvent 方法中返回 false 拦截消息后,消息不会直接停止传递,而是向父控件的 onTouchEvent 方法回传。
2.3.1 在 View 的 dispatchTouchEvent 方法中返回 false
2.3.2 在 ViewGroup2 的 dispatchTouchEvent 方法中返回 false
2.3.3 在 ViewGroup1 的 dispatchTouchEvent 方法中返回 false
2.3.4 在 Activity 的 dispatchTouchEvent 方法中返回 false
从上面这些消息传递过程中可以看出,在控件的 dispatchTouchEvent 方法中返回 false 后, ACTION_DOWN 消息会在 dispatchTouchEvent 这条线上停止传递,并向其父控件的 onTouchEvent 方法回传。
2.4 在 onTouchEvent 方法中拦截 ACTION_DOWN 消息
onTouchEvent 方法中,默认返回 false,表示不拦截; 当返回 true 时, 表示拦截。
先说结论:
无论哪个控件的 onTouchEvent 方法拦截 ACTION_DOWN 消息, 消息都会直接停止传递, 后面的父控件都不会接收到这个消息。
总结:
- dispatchTouchEvent 和 onTouchEvent 方法一旦返回true拦截消息, ACTION_DOWN 消息就会停止传递,正常流程下的后续节点都不会收到 ACTION_DOWN 消息了。
- 当 dispatchTouchEvent 方法,返回 false 时,首先会拦截 ACTION_DOWN 消息,消息不会继续传给子控件,而是传给父控件的 onTouchEvent,然后继续回传。
3. 在 onInterceptTouchEvent 方法中的 ACTION_DOWN 消息传递流程
3.1 onInterceptTouchEvent 方法的作用
首先,只有 ViewGroup 具有 onInterceptTouchEvent 方法, Activity 和 View 中都没有这个方法。
然后, onInterceptTouchEvent 方法其实就是一个拦截过滤器。每个 ViewGroup 每次在处理消息分发时,都会问一问拦截器要不要拦截(也就是这个消息要不要自己处理)。要处理就返回true,然后交给自己的 onTouchEvent 方法处理。如果不拦截,就继续往子控件传递。默认是不拦截的。
3.2 在 ViewGroup 的 onInterceptTouchEvent 方法中拦截 ACTION_DOWN 消息
3.2.1 仅在 ViewGroup2 的 onInterceptTouchEvent 方法中拦截 ACTION_DOWN 消息
3.2.2 仅在 ViewGroup1 的 onInterceptTouchEvent 方法中拦截 ACTION_DOWN 消息
3.2.3 在 ViewGroup2 的 onInterceptTouchEvent 和 onTouchEvent 方法中拦截 ACTION_DOWN 消息
总结:
在 ViewGroup 的 onInterceptTouchEvent 方法中拦截 ACTION_DOWN 消息,会改变 ACTION_DOWN 消息的流向,让它直接流向当前 ViewGroup 的 onTouchEvent 方法。
同样,如果 onTouchEvent 方法没有拦截继续拦截消息,则消息会继续传递。如果某个 onTouchEvent 方法中拦截消息的话, ACTION_DOWN 会直接停止传递。
4. 关于 ACTION_MOVE 和 ACTION_UP 消息传递流程
上面讲解的都是 ACTION_DOWN 消息的传递流程,我们知道 ACTION_DOWN 之后,是 ACTION_MOVE, 最后是 ACTION_UP 。
这两者与 ACTION_DOWN 的消息传递并不完全一样。
因为 ACTION_MOVE 和 ACTION_UP 的消息流向是完全一样的,所以只讲 ACTION_MOVE。
4.1 在 dispatchTouchEvent 方法中返回 true 的消息传递流程
结论:
在 dispatchTouchEvent 方法中返回 true 拦截消息后, ACTION_MOVE 消息的流向与 ACTION_DOWN 完全相同,消息会直接停止传递,后面的子控件都不会接收到这个消息。
4.1 在 onTouchEvent 方法中返回 true 的消息传递流程
这部分很复杂,稍后补充。
ACTION_MOVE 消息总结:
-
在 dispatchTouchEvent 方法中返回 true,拦截消息后, ACTION_MOVE 消息的流向与 ACTION_DOWN 消息的完全相同,消息会直接停止传递,后面的子控件都不会接收到这个消息。
-
无论 ACTION_DOWN 的最终流向如何,只要最终流到 onTouchEvent 方法中就行。假设控件 A 最终在 onTouchEvent 方法中消费了 ACTION_DOWN 消息,那么 ACTION_MOVE 消息的流向就是先流到控件 A 的 dispatchTouchEvent 方法中,最终直接流到控件 A 的 onTouchEvent 方法中。进而消息停止传递。
事件传递流程
Activity -> PhoneWindow -> DecorView -> ViewGroup -> ... -> view
典型的责任链模式
如果最后的view也没有消费事件,那么事件就会被依次向上回传直到Activity,如果Activity也没有处理该事件,那么事件就被抛弃。
既保证了事件的有序性,又非常的灵活。