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
事件
最近秋招大潮来临,各种公司内推电话面,每一次都感觉自己还是个渣,被问了很多不知道的东西,每一次面试都是查缺补漏,加油!预祝自己能找个好工作。