Android 事件分发机制一直来说都是一个比较难理解的知识内容,恰逢秋招,花了几天时间学习了一下,终于弄懂了是怎么回事,总结如下:
核心方法
事件分发机制主要有三个方法:
dispatchTouchEvent(),主要用于事件分发给子控件onIntercptTouchEvent(),主要用于是否拦截事件,ViewGroup独有onTouchEvent()方法,主要用于处理触摸事件
分发机制
当屏幕某一个控件被点击时,首先触发顶层布局的dispatchTouchEvent()方法,将事件进行向下传递,如果该布局是ViewGroup,则会先调用onInterceptTouchEvent()方法确定是否拦截该事件,如果返回true,则表示拦截该事件,不再往下传递,并调用自己的 onTouch()方法进行事件处理,否则分发给子控件如此循环。直至有子控件消费此事件。流程图如下,假设目前有三层布局,Activity -> LinearLayout -> Button
下图是分发业务流程图
代码分析
上图我刚开始看的时候也看不懂,下面从代码层面分析,根据具体情况分为多种情况,整个调用过程存在递归,先由上层往下分发事件,然后根据上层根据下层返回的数据进行处理,直至分发过程结束:
Activity接收到点击事件,首先进行分发,调用dispatchTouchEvent(),在这个函数里面,会进行事件的分发,调用子控件也即LinearLayout的dispatchTouchEvent()方法,根据LinearLayout的dispatchTouchEvent()的返回值进行判定,会有两种结果:如果返回
true,则表示子控件已经把事件消费掉了,则Activity的dispatchTouchEvent方法也返回true,代表已经处理了这个点击事件,事件分发过程结束。如果返回
false,则表示子控件并没有把事件消费掉,则Activity会调用自己的onTouchEvent()方法进行时间处理,一般来说我们不会覆写修改Activity的onTouchEvent()方法,该方法默认返回false,事件分发结束。
那么在
LinearLayout的dispatchTouchEvent()方法里什么时候会返回true,什么时候会返回false呢?与Activity有所不同的的是,LinearLayout是一个ViewGroup,多了一个onInterceptTouchEvent()方法,LinearLayout会先调用onInterceptTouchEvent()方法决定是否直接拦截事件,会有两种情况:onInterceptTouchEvent()返回true。那么LinearLayout不会像Activity一样无条件向下传递事件,而是直接拦截触摸事件,然后调用自己的onTouchEvent()方法,LinearLayout的dispatchTouchEvent()方法返回值取决于onTouchEvent()的返回值onInterceptTouchEvent()返回false,表示LinearLayout不拦截事件,会正常往下传递事件,在此例子中将会调用Button的dispatchTouchEvent()方法。Button的dispatchTouchEvent()方法返回值也有两种:- 返回
true。那么LinearLayout的dispatchTouchEvent()将直接结束,返回true,继续走Acticty的1.1流程。 - 返回
false。那么LinearLayout会调用自己的onTouchEvent()方法。和Activity类似,一般我们不会覆写这个方法,这个方法默认返回false,那么接下来继续走Activity的1.2流程。
- 返回
那么
Button的dispatchTouchEvent()方法里什么时候会返回true,什么时候会返回false呢?与LinearLayout不同,Button不是ViewGroup而是View,只有dispatchTouchEvent()和onTouchEvent方法,但是Button作为View比较特殊,还拥有一个方法onTouch(),Button的dispatchTouchEvent()方法流程如下。首先检查Button是否设定了触摸事件,也即onTouchListener是否为空,结果有两种:设定了触摸事件,则执行
onToucheListener的onTouch方法,Button的dispatchTouchEvent方法直接返回true,走LinearLayout的2.2.1流程未设定触摸事件,则执行
Button的onTouchEvent方法,根据onTouch方法返回值返回,如果是true,则Button的dispatchTouchEvent方法结束,走LinearLayout的2.2.2流程
使用伪代码的流程图如下:
当然,这个伪代码只是一个非常简略的版本,在实际的源码中,还有很多判定等条件和函数的具体实现,但是大体的流程就这以上这些。
额外知识
此外还有一些其他的知识:
onTouch方法调用时间先于onTouchEvent- 如果子
View不想让父控件拦截事件怎么办?可以在子类中调用requestDisallowInterceptTouchEvent方法来请求事件的直接下发 - 如果某控件
onTouchEvent方法对DOWN事件返回了false,也即不对DOWN事件进行处理,那么之后的UP和MOVE事件都不会下发给该控件。这也符合一般常识,如果一个控件对于手指触摸屏幕事件没有反应,那么对于接下来的手指移动和手指抬起的事件也应当无反应。 - 从 3 的另一个方向思考,如果某控件的
onTouchEvent对于DOWN事件返回了true,也即消费了该事件,那么接下来的事件都会自己处理,不会下发给子控件。 - 如果上一次
Button消费了DOWN事件,而之后来了一个MOVE事件被LinearLayout拦截了,那么会给Button传递一个CANCEL事件
最近秋招大潮来临,各种公司内推电话面,每一次都感觉自己还是个渣,被问了很多不知道的东西,每一次面试都是查缺补漏,加油!预祝自己能找个好工作。