一. 传递规则
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方法。
- 判断
本文详细解析了Android中MotionEvent的传递规则与分发过程,包括dispatchTouchEvent、onInterceptTouchEvent及onTouchEvent等方法的作用机制,同时介绍了如何通过requestDisallowInterceptTouchEvent干预事件分发。
1608

被折叠的 条评论
为什么被折叠?



