Android 事件分发机制详解(1)
Android事件分发机制详解(一)
所谓Android事件分发机制,其实也就是View的事件分发机制,在介绍事件的传递规则之前,首先我们要明白这里需要分析的对象MotionEvent。
MotionEvent类
在手指接触屏幕后所产生的事件封装成了MotionEvent类,典型的事件类型有如下几种:
- ACTION_DOWN:手机刚接触屏幕
- ACTION_MOVE:手指在屏幕上移动
- ACTION_UP:手指从屏幕上松开的一瞬间
- ACTION_CANCEL:结束事件(非人为)
正常情况下,一次手指触摸屏幕的行为会触发一系列事件,也就是我们说的事件序列,考虑如下几种情况:
- 点击行为:ACTION_DOWN -> ACTION_UP
- 滑动行为:ACTION_DOWN->ACTION_MOVE->…..->ACTION_MOVE->AVTION_UP
同一个事件序列是指从手指接触屏幕起得那一刻起,到手指离开屏幕那一刻结束,在这个过程中所产生的一系列事件,这个事件序列以down事件开始,中间含有数量不定的move事件,最终以up事件结束。
当MotionEvent产生后,系统要把这个事件传递给一个具体的View,而这个传递的过程也就是我们所说的事件分发。
事件分发的主要方法
- public boolean dispatchTouchEvent(MotionEvent ev):用来进行事件的分发。如果事件能够传递给当前View,那么此方法一定会调用,返回值表示是否消耗当前事件。
- public boolean onInterceptTouchEvent(MotionEvent ev):在dispatchTouchEvent方法中调用,用来进行事件的拦截,只有ViewGroup有这个方法,返回值表示是否拦截。若当前View拦截了某个事件,那么在同一个事件序列中,此方法不会被再次调用。
- public boolean onTouchEvent(MotionEvent ev):在dispatchTouchEvent方法中调用,用来进行事件的处理,返回值表示是否消耗当前事件。如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件。
事件分发机制
这里我们用一个实例来说明事件分发机制:
如图,当用户点击btn所在的区域时,android会怎么处理这个点击事件呢?
当一个事件产生后,它的传递过程遵循如下顺序:Activity->Window->View,即事件总是先传递给Activity,Acitivity再传递给Window,最后Window再传递给顶级View(ViewGroup继承于View,所以可是一个View)。顶级View接收到事件以后,再按照事件分发机制去分发事件。
在上图场景中,Window会将事件传递给LL_1,这时LL_1的dispatchTouchEvent方法就会被调用,如果该方法内的onInterceptTouchEvent方法返回true,则表示LL_1要拦截此次事件,该事件会交由LL_1处理,LL_1的OnTouchEvent方法会被调用。若onInterceptTouchEvent方法返回false,表示LL_1不拦截此次事件,该事件会继续向它的子元素传递。
LL_2接收到该事件时,会做和LL_1一样的事情,而当btn接收到该事件时,同样也会调用dispatchTouchEvent方法,而由于btn是一个View,没有OnInterceptTouchEvent方法,onTouchEvent方法会直接被执行,若onTouchEvent方法返回false,则表示该事件没有被消耗,那么它的父元素LL_2的onTouchEvent方法则会被调用,若LL_2的onTouchEvent方法依旧返回false,则调用LL_2父元素即LL_1的onTouchEvent方法,以此类推,若最终所有元素都不处理这个事件,那么这个事件最终会传递给Activity处理,即Activity的onTouchEvent方法会被调用。
下面是图解:
需要注意的是:
1)正常情况下,一个事件序列只能被一个View拦截且消耗,因为一旦一个元素拦截了某次事件,那么同一个时间序列内的所有事件都会直接交给它处理,因此同一个事件序列中的时间不能分别由两个View同时处理,但是通过特殊手段可以做到,例如一个View将本该自己处理的事情通过onTouchEvent强行传递给其他View处理。
2)某个ViewGroup一旦决定拦截,那么这一个事件序列都只能由它来处理(如果事件序列能够传递给它的话),并且它的onInterceptTouchEvent不会再被调用。
3)某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那么同一事件序列的其他事件都不会交给它来处理,并且事件将重新交个它的父元素来处理,即调用父元素的onTouchEvent来处理。
4)如果View不消耗除了ACTION_DOWN以外的其他事件,那么这个点击事件会消失,此时父元素的onTouchEvent方法不会被调用,最终这些消失的点击事件会传递给Activity处理。
5)ViewGroup默认不拦截任何事件。
6)View的onTouchEvent默认都会消耗事件(返回true),除非它是不可点击的(clickable和longclickable同时为false)。
7)View的enable属性不影响onTouchEvent的默认返回值。
8)事件传递过程是由外向内的,即事件总是传递给父元素,再由父元素分发给子元素,通过requestDisallowInterceptTouchEvent方法可以再子元素中干预父元素的事件分发过程,但是ACTION_DOWN除外。
OnTouchListener、OnClick、onTouchEvent之间的关系
当一个View需要处理事件时,如果它设置了OnTouchListener,那么OnTouchListener中的onTouch方法会被回调。这时事件如何处理得看onTouch的返回值,若返回false,则当前View的onTouchEvent方法会被调用;如果返回true,那么onTouchEvent方法将不会被调用。而在onTouchEvent方法中,如果当前View设置的有OnClickListener,那么它的onClick方法会被调用;由此可见三者之间的优先级关系为:OnTouchListener > onTouchEvent > OnClick。