Android面试必问之触摸事件传递机制
一、Activity的构成
一个Activity
包含了一个Window
对象,这个对象是由PhoneWindow
来实现的。PhoneWindow将DecorView作为整个应用窗口的根View
,而这个DecorView又将屏幕划分为两个区域
:一个是TitleView
,另一个是ContentView
,而我们平时所写的就是展示在ContentView中的。
二、触摸事件的类型
MotionEvent类,主要有三种事件类型:
- ACTION_DOWN
- ACTION_MOVE
- ACTION_UP
三、事件传递的三个阶段
dispatchTouchEvent
(分发):返回true表示事件被当前视图消费,返回super.dispatchTouchEvent表示继续分发事件onInterceptTouchEvent
(拦截):返回true表示拦截事件并调用onTouchEvent方法消费;返回false表示不拦截,继续传递给子视图。如果return super.onInterceptTouchEvent,分情况:
onTouchEvent
(消费):返回true表示当前视图处理该事件,返回false表示当前视图不处理事件,传递给父视图的onTouchEvent处理。如果return super.onTouchEvent,分情况:
ps:在Android系统中,拥有事件传递能力的类有:
1.Activity
:拥有分发和消费两个方法
2.ViewGroup
:拥有分发、拦截和消费三个方法
3.View
:拥有分发、消费两个方法
Activity对点击事件的分发过程
我们对触摸屏进行操作时,Linux就会收到相应的
硬件中断
,然后将中断加工成原始的输入事件并写入相应的设备节点
中。而我们的Android 输入系统所做的事情概括起来说就是监控这些设备节点
,当某个设备节点有数据可读时,将数据读出
并进行一系列的翻译加工,然后在所有的窗口中找到合适的事件接收者,并派发给它。
当点击事件产生后,事件会传递给当前的Activity
,由Activity中的PhoneWindow
完成,PhoneWindow再把事件处理工作交给DecorView
,之后再有DecorView将事件处理工作交给ViewGroup
。
五、View的事件分发机制
事件分发到
ViewGroup
的dispatchTouchEvent
方法,如果它的onInterceptTouchEvent
返回true
,则由自己处理
,这时如果它的mOnTouchListener
被设置,则onTouch
会被调用,否则onTouchEvent会被调用
。在onTouchEvent
中,如果设置了mOnCLickListener
,则onClick会被调用
。如果它的onInterceptTouchEvent
返回false
,则交给点击事件链上的子View处理
,如此循环,完成分发。
如下是View完整的点击事件传递流程:
六、点击事件分发的传递规则
点击事件分发的3个重要方法的关系,伪代码表示:
【一些重要结论】
- 事件传递的优先级:
onTouchListener.onTouch
>onTouchEvent
>onLongClickListener.onLongClick
>onClickListener.onClick
正常情况下,一个时间序列只能被一个View拦截且消耗
。因为一旦一个元素拦截了此事件,那么同一个事件序列内的所有事件都会直接交给它处理(即不会再调用这个View的拦截方法去询问它是否要拦截了,而是把剩余的ACTION_MOVE、ACTION_DOWN等事件直接交给它来处理)。特例:通过将重写View的onTouchEvent返回false可强行将事件转交给其他View处理。
- 如果View不消耗除ACTION_DOWN以外的其他事件,那么这个点击事件会消失,此时父元素的onTouchEvent并不会被调用,并且当前View可以持续收到后续的事件,最终这些消失的点击事件会传递给Activity处理。
ViewGroup默认不拦截任何事件
(返回false)。View的onTouchEvent默认都会消耗事件
(返回true),除非它是不可点击的(clickable和longClickable同时为false)。View的longClickable属性默认都为false,clickable属性要分情况,比如Button的clickable属性默认为true,而TextView的clickable默认为false。- 通过requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的事件分发过程,但是ACTION_DOWN事件除外。