事件的分发和消费机制
一、简介:
Activity或View类的onTouchEvent()回调函数会接收到touch事件。
一个完整的手势是从ACTION_DOWN开始,到ACTION_UP结束。
简单的情况下,我们只需要在onTouchEvent()中写个switch case语句,处理各种事件(Touch Down、Touch Move、Touch Up等),但是比较复杂的动作就需要更多的处理了。
ViewGroup作为一个parent是可以截获传向它的child的touch事件的。
如果一个ViewGroup的onInterceptTouchEvent()方法返回true,说明Touch事件被截获,子View不再接收到Touch事件,而是转向本ViewGroup的onTouchEvent()方法处理。从Down开始,之后的Move,Up都会直接在onTouchEvent()方法中处理。
先前还在处理touch event的child view将会接收到一个ACTION_CANCEL。
如果onInterceptTouchEvent()返回false,则事件会交给child view处理。
Android中提供了ViewGroup、View、Activity三个层次的Touch事件处理。
处理过程是按照Touch事件从上到下传递,再按照是否消费的返回值,从下到上返回,即如果View的onTouchEvent返回false,将会向上传给它的parent的ViewGroup,如果ViewGroup不处理,将会一直向上返回到Activity。
即隧道式向下分发,然后冒泡式向上处理。
Android 中与 Touch 事件相关的方法包括:dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEvent(MotionEvent ev)、onTouchEvent(MotionEvent ev);能够响应这些方法的控件包括:ViewGroup、View、Activity。方法与控件的对应关系如下表所示:
Touch 事件相关方法方法功能ViewGroupViewActivity
public booleandispatchTouchEvent(MotionEvent ev)事件分发YesYesYes
public booleanonInterceptTouchEvent(MotionEvent ev)事件拦截YesYes / NoNo
public booleanonTouchEvent(MotionEvent ev)事件响应YesYesYes
从这张表中我们可以看到 ViewGroup 和 View 对与 Touch 事件相关的三个方法均能响应,而 Activity 对 onInterceptTouchEvent(MotionEvent ev) 也就是事件拦截不进行响应。另外需要注意的是 View 对 onInterceptTouchEvent(MotionEvent ev) 的响应的前提是可以向该 View 中添加子 View,如果当前的 View 已经是一个最小的单元View(比如TextView),那么就无法向这个最小 View 中添加子 View,也就无法向子 View 进行事件的拦截,所以它没有onInterceptTouchEvent(MotionEvent ev)。
三个方法的用法:
dispatchTouchEvent()用来分派事件。
其中调用了onInterceptTouchEvent()和onTouchEvent(),一般不重写该方法
onInterceptTouchEvent()用来拦截事件。
ViewGroup类中的源码实现就是{return false;}表示不拦截该事件,
事件将向下传递(传递给其子View);
若手动重写该方法,使其返回true则表示拦截,事件将终止向下传递,
事件由当前ViewGroup类来处理,就是调用该类的onTouchEvent()方法
onTouchEvent()用来处理事件。
返回true则表示该View能处理该事件,事件将终止向上传递(传递给其父View);
返回false表示不能处理,则把事件传递给其父View的onTouchEvent()方法来处理
【注】:ViewGroup的某些子类(GridView、ScrollView...)重写了onInterceptTouchEvent()方法,当发生ACTION_MOVE事件时,返回true进行拦截。
一、Touch事件分析
(一)、事件分发:public booleandispatchTouchEvent(MotionEvent ev)
Touch事件发生时Activity的dispatchTouchEvent(MotionEvent ev)方法会以隧道方式(从根元素依次往下传递直到最内层子元素或在中间某一元素中由于某一条件停止传递)将事件传递给最外层View的dispatchTouchEvent(MotionEvent ev)方法,并由该View的dispatchTouchEvent(MotionEvent ev)方法对事件进行分发。
(二)、事件拦截:public booleanonInterceptTouchEvent(MotionEvent ev)
在外层View的dispatchTouchEvent(MotionEvent ev)方法返回系统默认的super.dispatchTouchEvent(ev)情况下,事件会自动的分发给当前View的onInterceptTouchEvent方法。onInterceptTouchEvent的事件拦截逻辑如下:
•如果onInterceptTouchEvent返回true,则表示将事件进行拦截,并将拦截到的事件交由当前View的onTouchEvent进行处理;
•如果onInterceptTouchEvent返回false,则表示将事件放行,当前View上的事件会被传递到子View上,再由子View的dispatchTouchEvent来开始这个事件的分发;
•如果onInterceptTouchEvent返回super.onInterceptTouchEvent(ev),事件默认不会被拦截,并将拦截到的事件交由当前View的onTouchEvent进行处理。
(三)、事件响应:public booleanonTouchEvent(MotionEvent ev)
在dispatchTouchEvent返回super.dispatchTouchEvent(ev)并且onInterceptTouchEvent返回true或返回super.onInterceptTouchEvent(ev)的情况下onTouchEvent会被调用。onTouchEvent的事件响应逻辑如下:
•如果事件传递到当前View的onTouchEvent方法,而该方法返回了false,那么这个事件会从当前View向上传递,并且都是由上层View的onTouchEvent来接收,如果传递到上面的onTouchEvent也返回false,这个事件就会“消失”,而且接收不到下一次事件。
•如果返回了true则会接收并消费该事件。
•如果返回super.onTouchEvent(ev)默认处理事件的逻辑和返回false时相同。
onInterceptTouchEvent用于改变事件的传递方向。决定传递方向的是返回值,返回为false时事件会传递给子控件,返回值为true时事件会传递给当前控件的onTouchEvent(),这就是所谓的Intercept(拦截)。
正确的使用方法是,在此方法内仅判断事件是否需要拦截,然后返回。即便需要拦截也应该直接返回true,然后由onTouchEvent方法进行处理。
onTouchEvent用于处理事件,返回值决定当前控件是否消费(consume)了这个事件。尤其对于ACTION_DOWN事件,返回true,表示我想要处理后续事件;返回false,表示不关心此事件,并返回由父类进行处理。
可能你要问是否消费了又区别吗,反正我已经针对事件编写了处理代码?答案是有区别!比如ACTION_MOVE或者ACTION_UP发生的前提是一定曾经发生了ACTION_DOWN,如果你没有消费ACTION_DOWN,那么系统会认为ACTION_DOWN没有发生过,所以ACTION_MOVE或者ACTION_UP就不能被捕获。
在没有重写onInterceptTouchEvent()和onTouchEvent()的情况下(他们的返回值都是false)
Android系统中的每个View的子类都具有下面三个和TouchEvent处理密切相关的方法:
1)public boolean dispatchTouchEvent(MotionEvent ev)这个方法用来分发TouchEvent
2)public boolean onInterceptTouchEvent(MotionEvent ev)这个方法用来拦截TouchEvent
3)public boolean onTouchEvent(MotionEvent ev)这个方法用来处理TouchEvent
1、如果dispatchTouchEvent返回true,则交给这个view的onTouchEvent处理, 如果最终需要处理事件的view的onTouchEvent()返回了false,那么该事件将被传递至其上一层次的view的onTouchEvent()处理。如果最终需要处理事件的view的onTouchEvent()返回了true,那么后续事件将可以继续传递给该view的onTouchEvent()处理。
2、如果dispatchTouchEvent6返回false,则交给这个view的interceptTouchEvent方法来决定是否
要拦截这个事件,如果interceptTouchEvent返回true,表示拦截掉了,则交给它的onTouchEvent来处理,如果interceptTouchEvent返回false,那么就传递给子view,由子view的dispatchTouchEvent再来开始这个事件的分发。
3、如果事件传递到某一层的子view的onTouchEvent上了,这个方法返回了false,那么这个事件
会从这个view往上传递,都是onTouchEvent来接收。如果传递到最上面的onTouchEvent也返回false的话,这个事件就会“消失”,而且接收不到下一次事件。
【备注:】
蓝色区域为MyViewGroup、绿色为MyViewGroupInner、红色为MyView。分别继承于LinearLayout、LinearLayout和TextView。
1、核心代码:
【以下动作均为点击自定义View】
一、无拦截,无消费:12个
二、ViewGroup拦截,无消费:7个
03-31 18:50:26.344: I/MainActivity(15304): --->dispatchTouchEvent: 0
03-31 18:50:26.344: I/MyViewGroup(15304): --->dispatchTouchEvent: 0
03-31 18:50:26.372: I/MyViewGroup(15304): --->onInterceptTouchEvent: 0
03-31 18:50:26.392: I/MyViewGroup(15304): --->onTouchEvent: 0
03-31 18:50:26.402: I/MainActivity(15304): --->onTouchEvent: 0
03-31 18:50:26.526: I/MainActivity(15304): --->dispatchTouchEvent: 1
03-31 18:50:26.526: I/MainActivity(15304): --->onTouchEvent: 1
三、无拦截,View消费:14个
四、无拦截,仅有ViewGroup消费:12个
五、无拦截,Activity消费:12个
六、无拦截、无消费,仅在ViewGroup上设置单击监听,执行单击动作后:13个
七、无拦截、无消费,仅在ViewGroup上设置长按监听和单击监听,执行长按动作后:(如果长按返回false:14个 , 如果长按返回true:13个)
八、无拦截、无消费,仅仅在View上设置长按监听和单击监听,执行单击动作后:15个
九、无拦截、无消费,在View上设置长按监听和单击监听,执行长按动作后:(如果长按返回false:16个 , 如果长按返回true:15个)
十、无拦截、无消费,仅在View上设置clickable=true:14个
十一、无拦截、无消费,仅在ViewGroupInner上设置clickable=true:13个
A:dispatch:0
G:dispatch:0
G:intercept:0
Gi:dispatch:0
Gi:intercepte:0
V:dispatch:0
V:touch:0
Gi:touch:0
A:dispatch:1
G:disaptch:1
G:intercept:1
Gi:dispatch:1
Gi:touch:1
【备注:】
onLongClick按下到一定的时间就调用了,在ACTION_UP之前调用。如果长按返回false,则长按结束的ACTION_UP调用onClick(onClick是ACTION_UP之后调用的);如果长按返回true,onLongClick后不再调用onClick。
Click事件处理
Click事件:View的短按和长按都是注册监听器的(setListener):
onClick是在ACTION_UP之后执行的。
onLongClick则是按下到一定时间之后执行的,这个时间是ViewConfiguration中的:
private static final int TAP_TIMEOUT =180;//180毫秒
这里需要注意onLongClick的返回值,如果是false,则onLongClick之后,手指抬起,ACTION_UP之后还是会执行到onClick;但是如果onLongClick返回true,则不会再调用onClick。
【备注:】
如果一个View是Clickable或者longClickable,onTouchEvent()就直接返回true,表示该View就一直消费Touch事件。也就是说,一个clickable或者longclickable的View是一直消费Touch事件的,而一般的View既不是clickable也不是longclickable的(即不会消费Touch事件,只会执行ACTION_DOWN而不会执行ACTION_MOVE和ACTION_UP)。
Button是clickable的,可以消费Touch事件,但是我们可以通过setClickable()和setLongClickable()来设置View是否为clickable和longClickable。当然还可以通过重写View的onTouchEvent()方法来控制Touch事件的消费与否。