一. 传递规则
MotionEvent
的分发主要由下面三个方法来完成:public boolean dispatchTouchEvent(MotionEvent ev)
对于一个根
ViewGroup
,点击时间产生后,会首先调用这个方法。它将根据onInterceptTouchEvent
的返回值,来决定是否对子View继续分发。public boolean onInterceptTouchEvent(MotionEvent ev)
在
dispatchTouchEvent
中被调用,并获取返回值。- 如果返回
true
:ViewGroup
将拦截当前事件,事件将由ViewGroup
来处理,它的onTouchEvent
将会被调用。 - 如果返回
false
:ViewGroup
会将当前时间传递给子View,接着子View的dispatchTouchEvent
将会被调用,知道事件被最终处理。
- 如果返回
public boolean onTouchEvent(MotionEvent ev)
当时间分发到View时,如果View注册了
OnTouchListener
,那么该接口的onTouch
方法会被回调,回调的结果将影响后面的处理过程:- 如果
onTouch
返回true:View的onTouchEvent
将会被调用 - 如果
onTouch
返回false:View的onTouchEvent
将不会被调用
- 如果
当一个点击事件产生后,其传递过程如下:
Activity -> Window -> View
- 当View的
onTouchEvent
返回false,那么它的父容器的onTouchEvent
将会被调用,以此类推。 - 如果所有的
View
都没有处理这个事件,那么最终会传递个Activity
处理。
- 当View的
- 事件传递细则:
- 同一个事件序列,是指手指触摸屏幕的那一刻开始,到手指离开屏幕的那一刻结束。
- 事件序列以
down
事件开始,以up
事件结束。
- 事件序列以
- 正常情况下,一个事件只能被一个View拦截且消耗。
- 如果某个View拦截了事件,那么整个事件序列都只能由它来处理,它的
onInterceptTouchEvent
也将不会再被调用。 - 某个View一旦开始处理事件,如果它不消耗
ACTION_DOWN
事件,那么同一事件序列中的其它事件都不会交给它来处理,并且事件将交由它的父元素去处理,此时父元素的onTouchEvent
会被调用。
- 一旦决定要处理事件,就必须消耗掉事件,否则父元素将重新参与处理。
- 如果View不消耗除了down事件以外的其它事件,那么这个点击事件将会消失,此时父元素的
onTouchEvent
不会被调用。
- 并且当前View可以持续收到后续的事件,最终这些消失的点击事件会传递给Activity管理。
ViewGroup
默认不拦截事件。ViewGroup
的onInterceptTouchEvent
默认实现返回false。- View没有实现
onInterceptTouchEvent
方法,一旦有点击事件传递给它,它的onTouchListener
就会被调用。 - View的
onTouchEvent
默認返回true,即默认会消耗事件。除非View是不可点击clickable == false
, 而且不可长按longClickable == false
。
longClickable
默认一般都是false
。- 有些View的
clickable
默认为true,比如Button
需要交互的控件。 - 有些View的
clickable
默认为false,比如TextView
这种无需交互的控件。 - View的
enable
属性,不影响onTouchEvent
的默认返回值。
onClick
会发生的前提:当前View是clickable
,而且它收到了 down 和 up事件。- 事件传递过程总是先传给父元素,再由父元素分发给子View。
- 通过
requestDisallowInterceptTouchEvent
方法,可以在子元素中干预父元素的事件分发过程。但ACTION_DOWN
事件除外。
- 通过
- 同一个事件序列,是指手指触摸屏幕的那一刻开始,到手指离开屏幕的那一刻结束。
二. 分发过程分析
Activity对点击事件的分发源码:
//Activity.java public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }
事件首先会交给Window进行分发。
- 如果Window所包含的所有View都返回了false,事件将交回给Activity来处理,Activity的
onTouchEvent
将会被调用。 - 如果Window中有View处理了事件,
getWindow().superDispatchTouchEvent(ev)
将会返回true,此时事件已经被处理,分发结束。
- 如果Window所包含的所有View都返回了false,事件将交回给Activity来处理,Activity的
Window
处理事件
接口Window
的唯一实现类是PhoneWindow
,superDispatchTouchEvent
的实现如下:public boolean superDispatchTouchEvent(MotionEvent event){ return mDecor.superDispatchTouchEvent(event); }
DecorView是
Window
中最顶层的View,我们通过setContentView
设置的View是DecorView的一个子View。从这里开始,事件已经传递到顶级View了。
顶级View对点击事件的分发
分发过程我们在上一个小节已经介绍过了,这里再总结一下。
实际上分发过程类似于遍历一个View树:- 每当分发到一个非叶子节点,就会调用
dispatchTouchEvent
:
- 如果该节点的
onInterceptTouchEvent
返回true,表示拦截事件,则事件由该节点处理。 - 如果该节点的
onInterceptTouchEvent
返回false,则将事件分发给其儿子节点。
- 如果该节点的
当一个节点接收到事件分发时,可以选择是否自己来处理,通过
onTouchEvent
的返回值分别:- true:自己处理事件,分发结束
- false:不处理事件,事件将继续分发
此外,每个节点的
onTouchEvent
都有默认返回值:- 非叶节点或根,即
ViewGroup
,那么onTouchEvent
默认返回false - 叶节点一般默认返回true。
- 一些叶节点天生自带交互属性,比如Button,默认返回true。
- 如果所有的节点都没有处理事件,最终将交回给Activity处理。
- 每当分发到一个非叶子节点,就会调用
非ViewGroup的View对点击事件的处理
- 判断
onTouchListener
是否被用户注入。如果已注入,判断onTouch
的返回值:
onTouch
返回true:onTouchEvent
将不会被调用。
2,如果
onTouchListener
为空,或者其onTouch
返回false,onTouchEvent
将会被调用。- 当
onTouchEvent
被调用时:
- 如果clickable或longClickable有一个为true,将消耗事件,然后返回true。
- 当事件是ACTION_UP时,将触发
performClick
方法。如果View注入了OnClickListener
,那么performClick
内部会调用它的onClick
方法。
- 判断