Android事件分发机制详解

一、概要

事件分发是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子类):
ActivityFrameLayout、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,见蓝线的走势。

五、总结

这里写图片描述

最后再看下这张图,有种豁然开朗的感觉。

六、参考文章

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值