一、概要
事件分发是Android应用程序中比不可少的机制之一。由于移动设备的屏幕大小所限,Android在设计之初使用了树的数据结构为屏幕中的各种组件层级叠放。那么这就出现一个问题,当一次触摸事件发生的时候(从手指触摸到屏幕中的某个控件,到最后手指从屏幕中抬起,中间可能还包含了一系列的滑动等操作,整个过程成为一次触摸事件),这个事件该由谁负责响应,是触摸到的控件还是它底下一层父控件?当然还有更特殊的,那就是不同的事件(如上下滑动和左右滑动)需要不同层级的控件处理,这就是所谓的滑动冲突问题,当然这是后话。先说说最常规的问题吧。
二、事件分发涉及到的主要方法
理解事件分发机制,一定要记住下面这几个方法:
public boolean dispatchTouchEvent(MotionEvent ev); //用来分派event
public boolean onInterceptTouchEvent(MotionEvent ev); //用来拦截event
public boolean onTouchEvent(MotionEvent ev); //用来处理event
拥有这三个方法的类:
Activity类: | View容器(ViewGroup的子类): | View控件(非ViewGroup子类): |
---|---|---|
Activity | FrameLayout、LinearLayout、ListView、ScrollView…… | Button、TextView、EditText…… |
dispatchTouchEvent(); onTouchEvent(); | dispatchTouchEvent(); onInterceptTouchEvent(); onTouchEvent(); | dispatchTouchEvent(); onTouchEvent(); |
三个方法的用法:
- dispatchTouchEvent():用来分派事件。
其中调用了onInterceptTouchEvent()和onTouchEvent(),一般不重写该方法 - onInterceptTouchEvent(): 用来拦截事件。
ViewGroup类中的源码实现就是{return false;}表示不拦截该事件,
事件将向下传递(传递给其子View);
若手动重写该方法,使其返回true则表示拦截,事件将终止向下传递,
事件由当前ViewGroup类来处理,就是调用该类的onTouchEvent()方法 - onTouchEvent(): 用来处理事件。
返回true则表示该View能处理该事件,事件将终止向上传递(传递给其父View);
返回false表示不能处理,则把事件传递给其父View的onTouchEvent()方法来处理。
三、ACTION_DOWN的事件分发
下面看张图,把上面三个方法联系起来:
事件分发理解图,转自kelin的简书,这是我见过的最清晰易懂的事件分发流程图
我们可以简单地理解为一个Activity中有个ViewGroup作为根元素,他里面有个子View。
需要注意的一点是,这张图是针对ACTION_DOWN的事件分发流程。(而ACTION _MOVE、ACTION _UP与这张图中描述流程稍有不同,其实后两种动作是根据同一事件中ACTION _DOWN的操作反馈而表现为不同的流程,后面会讲。)
纵观这张图,可以分为三行,每一行表示一个分发的类,第一行是Activity,它有两个方法:dispatchTouchEvent()和onTouchEvent();第二行是ViewGroup:它多了一个onInterceptTouchEvent()方法;最后一行仍然是只有两个方法dispatchTouchEvent()和onTouchEvent()。
针对每个方法:最左边一列的dispatchTouchEvent()方法,如果返回true,表示自己消费事件,所谓消费,就是指事件到此为止,不再向任何地方传递;若返回false,表示该事件将传递给此View上一级的onTouchEvent()方法(对于Activity,它不能向上传递了,所以如果它的dispatchTouchEvent()返回false,表示消费了事件,这与它返回true的效果相同);如果返回super.dispatchTouchEvent(),即父类的实现(默认都是返回父类的实现),那么该事件将传递给它的下一级的dispatchTouchEvent()方法(对于ViewGroup,会先将事件发给自己的onInterceptTouchEvent方法,只有返回了false或super,才传递给它下一级的dispatchTouchEvent(),onInterceptTouchEvent方法默认返回false (super);对于View来说,没有子View了,所以事件将传给自己的onTouchEvent())。
对于onTouchEvent方法:若返回了true,表示消费事件,不再向上级传递,若返回super.onTouchEvent()或是false,那么该事件将向上级传递,最后Activity的onTouchEvent()将返回true,因为若事件传递到Activity了,它是最后一级了,只能由它来处理了。
所以,若事件不中断,那么整个事件将经历一个U型图,从左上角的Activity的dispatchTouchEvent传到有上角的onTouchEvent上。如下图所示。
小结一下:
- 对于 dispatchTouchEvent,onTouchEvent,return true是终结事件传递。return false 是回溯到父View的onTouchEvent方法。
- ViewGroup 想把自己分发给自己的onTouchEvent,需要拦截器onInterceptTouchEvent方法return true 把事件拦截下来。
- ViewGroup 的拦截器onInterceptTouchEvent 默认是不拦截的,所以return super.onInterceptTouchEvent()=return false;
- View 没有拦截器,为了让View可以把事件分发给自己的onTouchEvent,View的dispatchTouchEvent默认实现(super)就是把事件分发给自己的onTouchEvent。
对于dispatchTouchEvent的几种流向:
1、注:——> 后面代表事件目标需要怎么做。
自己消费,终结传递。——->return true ;
2、给自己的onTouchEvent处理——-> 调用super.dispatchTouchEvent()系统默认会去调用 onInterceptTouchEvent,在onInterceptTouchEvent return true就会去把事件分给自己的onTouchEvent处理。
3、传给子View——>调用super.dispatchTouchEvent()默认实现会去调用 onInterceptTouchEvent 在onInterceptTouchEvent return false,就会把事件传给子类。
4、不传给子View,事件终止往下传递,事件开始回溯,从父View的onTouchEvent开始事件从下到上回归执行每个控件的onTouchEvent——->return false;
需要注意的是 View没有子View了 ,所以它的dispatchTouchEvent()方法就相当于拦截了事件。
对于onTouchEvent方法:它只有两种流向:
1、自己消费掉,事件终结,不再传给谁—–>return true;
2、继续从下往上传,不消费事件,让父View也能收到到这个事件—–>return false;View的默认实现是不消费的。所以super==false。
对于onInterceptTouchEvent(),它的流向也只有两种:
1、拦截下来,给自己的onTouchEvent处理—>return true;
2、不拦截,把事件往下传给子View—->return false,ViewGroup默认是不拦截的,所以super==false;
四、ACTION_MOVE和ACTION _UP的事件分发
在最开始研究事件分发的时候,一直有个问题,onTouchEvent()中处理了某个事件以后,无论是返回true还是false,有啥区别吗?反正不管返回啥,我都是处理了。
后来才知道,原来ACTION_MOVE、ACTION _UP 与ACTION _DOWN的分发流程是不太一样的,前两者的执行流程其实是由后者的拦截以及行为来决定的。
举个栗子:
重写四个类:
总统 –> MyActivity
省长 –> MyFrameLayout
市长 –> MyLinearLayout
农民 –> MyTextView
【举个通俗易懂的例子】:
总统对省长说:我要吃红烧鱼
省长对市长说:你做个红烧鱼
市长对县长说:你做个红烧鱼
县长对农民说:你做个红烧鱼
……(农民做呀做,没做出来)
农民说:我尽力了,但真心不会做呀,饶了我吧
县长说:你个笨蛋,下次不找你了,看我来做
……(县长做呀做,没做出来)
县长对市长说:我尽力了,非常抱歉,我不会做
市长说:你个废物,要你何用,只能我自己来做了
……(市长做呀做,做成功了)
市长对省长说:红烧鱼做好了
省长说:不错,下次有事还找你
省长对总统说:红烧鱼做好了
总统说:不错,下次有事还找你
总统对省长说:我要吃水煮鱼
省长对市长说:你做个水煮鱼
市长说:县长连红烧鱼都搞不定,这次就不找他了,我自己亲自来做
……(市长做呀做,又成功了)
市长对省长说:水煮鱼做好了
省长说:不错,下次有事还找你
省长对总统说:水煮鱼做好了
总统说:不错,下次有事还找你
注意:吃红烧鱼就相当于ACTION_DOWN ,吃水煮鱼相当于ACTION _MOVE或是ACTION _UP,所以上面整个吃鱼的过程相当于一个触摸事件。
按常理,领导都会把任务向下分派,一旦下面的人把事情做不好,就不会再把后续的任务交给下面的人来做了,只能自己亲自做,如果自己也做不了,就只能告诉上级不能完成任务,上级又会重复他的过程。
另外,领导都有权利拦截任务,对下级隐瞒该任务,而直接自己去做,如果做不成,也只能向上级报告不能完成任务。
1)在这里,MyTextView的clickable属性是false,所以它的onTouchEvent默认返回false;
看到了吧,红色的线表示Action_Down,根据上一节的流程图,整个流程符合一个U型图的样子。对于Down事件,这就相当于所有的ViewGroup都是默认实现,没有拦截,而交给最后一级的MyTextView处理,但是它的onTouchEvent返回false,表示自己并没有能力处理,只能交给向上一级(MyLinearLayout)处理,但是上一级也没能力处理(返回false),接着交给上级处理,照样没能力处理,最后交给Activity的onTouchEvent处理。如果换成吃鱼的栗子,可以理解为,总统(Activity)把做鱼的工作分派给了(dispatchEvent()返回super.dispatchEvent())省长(MyFrameLayout),省长又把这个活给了市长(MyLinearLayout),市长给了农民(MyTextView);农民并没有做出来(MyTextView的onTouchEvent()返回false),只能市长做,但是市长也没做出来(MyLinearLayout的onTouchEvent()返回false),交给省长,但也没做出来(MyFrameLayout的onTouchEvent()返回false),最后总统只能一气之下自己做了。
后来要做水煮鱼了,总统觉得既然你们连红烧鱼都没做出来,那水煮鱼就我自己做好了,于是就能看到那条蓝线为何这么画——Activity的dispatchEvent之间传给了自己的onTouchEvent。
2)下面吧MyTextView的clickable属性手动改成true(即MyTextView的onTouchEvent返回true):
可以看出,如果DOWN事件分发到了MyTextView(红烧鱼交给了农民做),MyTextView的onTouchEvent返回了true(农民做出来了),那么MOVE事件(蓝线)也将传递到MyTextView由它处理(红烧鱼农民都做出来了,水煮鱼也让农民做,它肯定也能做出来),所有上级都不会收到这个事件(红烧鱼和水煮鱼农民都做出来了,没市长和省长啥事了)。
3)手动重写LinearLayout的onInterceptTouchEvent()方法,使其返回true,拦截事件,再重写onTouchEvent()方法,返回true,程序输出:
这个栗子里,DOWN事件分发到了MyLinearLayout中,onInterceptTouchEvent()返回true,它要自己来处理(市长要自己做红烧鱼),而onTouchEvent返回true(表示市长把红烧鱼做出来了),那么MOVE事件也将由他处理。(水煮鱼也让市长做,他肯定能做出来)
4)最后一个栗子,将MyLinearLayout的onInterceptTouchEvent返回true、onTouchEvent返回false;将MyFrameLayout的onTouchEvent返回true。
由于MyLinearLayout的onInterceptTouchEvent返回true,即MyLinearLayout拦截了DOWN事件(市长要做红烧鱼),但是onTouchEvent返回false(市长没做出来),于是交给MyFrameLayout处理,而它的onTouchEvent返回true(省长把红烧鱼做出来了);那么在处理MOVE事件的时候,MyFrameLayout认为你MyLinearLayout的Down事件都没处理好,于是MOVE将由自己亲自处理(水煮鱼不再给市长做了,直接省长自己做),在这之间,MOVE事件都不需要MyFrameLayout的拦截了,dispatchTouchEvent后直接执行onTouchEvent,见蓝线的走势。
五、总结
最后再看下这张图,有种豁然开朗的感觉。