public boolean dispatchTouchEvent(MotionEvent event)public boolean onTouchEvent(MotionEvent event)ViewGroup.java
public boolean dispatchTouchEvent(MotionEvent event)public boolean onTouchEvent(MotionEvent event) public boolean onInterceptTouchEvent(MotionEvent event)在View和ViewGroup中都存在dispatchTouchEvent和onTouchEvent方法,但是在ViewGroup中还有一个onInterceptTouchEvent方法,那这些方法都是干嘛的呢?别急,我们先看看他们的返回值。这些方法的返回值全部都是boolean型,为什么是boolean型呢,看看本文的标题,“事件传递”,传递的过程就是一个接一个,那到了某一个点后是否要继续往下传递呢?你发现了吗,“是否”二字就决定了这些方法应该用boolean来作为返回值。没错,这些方法都返回true或者是false。在Android中,所有的事件都是从开始经过传递到完成事件的消费,这些方法的返回值就决定了某一事件是否是继续往下传,还是被拦截了,或是被消费了。
public class RTButton extends Button {public RTButton(Context context, AttributeSet attrs) {super(context, attrs);}@Overridepublic boolean dispatchTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:System.out.println("RTButton---dispatchTouchEvent---DOWN");break;case MotionEvent.ACTION_MOVE:System.out.println("RTButton---dispatchTouchEvent---MOVE");break;case MotionEvent.ACTION_UP:System.out.println("RTButton---dispatchTouchEvent---UP");break;default:break;}return super.dispatchTouchEvent(event);}@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:System.out.println("RTButton---onTouchEvent---DOWN");break;case MotionEvent.ACTION_MOVE:System.out.println("RTButton---onTouchEvent---MOVE");break;case MotionEvent.ACTION_UP:System.out.println("RTButton---onTouchEvent---UP");break;default:break;}return super.onTouchEvent(event);}}在RTButton中我重写了dispatchTouchEvent和onTouchEvent方法,并获取了MotionEvent各个事件状态,打印输出了每一个状态下的信息。然后在activity_main.xml中直接在根布局下放入自定义的按钮RTButton。<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/myLayout"android:layout_width="match_parent"android:layout_height="match_parent" ><com.ryantang.eventdispatchdemo.RTButton android:id="@+id/btn"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="Button"/></LinearLayout>接下来在Activity中为RTButton设置onTouch和onClick的监听器来跟踪事件传递的过程,另外,Activity中也有一个dispatchTouchEvent方法和一个onTouchEvent方法,我们也重写他们并输出打印信息。
public class MainActivity extends Activity {private RTButton button;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);button = (RTButton)this.findViewById(R.id.btn);button.setOnTouchListener(new OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:System.out.println("RTButton---onTouch---DOWN");break;case MotionEvent.ACTION_MOVE:System.out.println("RTButton---onTouch---MOVE");break;case MotionEvent.ACTION_UP:System.out.println("RTButton---onTouch---UP");break;default:break;}return false;}});button.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {System.out.println("RTButton clicked!");}});}@Overridepublic boolean dispatchTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:System.out.println("Activity---dispatchTouchEvent---DOWN");break;case MotionEvent.ACTION_MOVE:System.out.println("Activity---dispatchTouchEvent---MOVE");break;case MotionEvent.ACTION_UP:System.out.println("Activity---dispatchTouchEvent---UP");break;default:break;}return super.dispatchTouchEvent(event);}@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:System.out.println("Activity---onTouchEvent---DOWN");break;case MotionEvent.ACTION_MOVE:System.out.println("Activity---onTouchEvent---MOVE");break;case MotionEvent.ACTION_UP:System.out.println("Activity---onTouchEvent---UP");break;default:break;}return super.onTouchEvent(event);}}代码部分已经完成了,接下来运行工程,并点击按钮,查看日志输出信息,我们可以看到如下结果: 
通过日志输出可以看到,首先执行了Activity的dispatchTouchEvent方法进行事件分发,在MainActivity.java代码第55行,dispatchTouchEvent方法的返回值是super.dispatchTouchEvent(event),因此调用了父类方法,我们进入Activity.java的源码中看看具体实现。
Activity.java
/*** Called to process touch screen events. You can override this to* intercept all touch screen events before they are dispatched to the* window. Be sure to call this implementation for touch screen events* that should be handled normally.* * @param ev The touch screen event.* * @return boolean Return true if this event was consumed.*/public boolean dispatchTouchEvent(MotionEvent ev) {if (ev.getAction() == MotionEvent.ACTION_DOWN) {onUserInteraction();}if (getWindow().superDispatchTouchEvent(ev)) {return true;}return onTouchEvent(ev);}从源码中可以看到,dispatchTouchEvent方法只处理了ACTIONDOWN事件,前面提到过,所有的事件都是以按下为起点的,所以,Android认为当ACTIONDOWN事件没有执行时,后面的事件都是没有意义的,所以这里首先判断ACTION_DOWN事件。如果事件成立,则调用了onUserInteraction方法,该方法可以在Activity中被重写,在事件被分发前会调用该方法。该方法的返回值是void型,不会对事件传递结果造成影响,接着会判断getWindow().superDispatchTouchEvent(ev)的执行结果,看看它的源码:/*** Used by custom windows, such as Dialog, to pass the touch screen event* further down the view hierarchy. Application developers should* not need to implement or call this.**/public abstract boolean superDispatchTouchEvent(MotionEvent event);通过源码注释我们可以了解到这是个抽象方法,用于自定义的Window,例如自定义Dialog传递触屏事件,并且提到开发者不需要去实现或调用该方法,系统会完成,如果我们在MainActivity中将dispatchTouchEvent方法的返回值设为true,那么这里的执行结果就为true,从而不会返回执行onTouchEvent(ev),如果这里返回false,那么最终会返回执行onTouchEvent方法,由此可知,接下来要调用的就是onTouchEvent方法了。别急,通过日志输出信息可以看到,ACTION_DOWN事件从Activity被分发到了RTButton,接着执行了onTouch和onTouchEvent方法,为什么先执行onTouch方法呢?我们到RTButton中的dispatchTouchEvent看看View中的源码是如何处理的。
/*** Pass the touch screen motion event down to the target view, or this* view if it is the target.** @param event The motion event to be dispatched.* @return True if the event was handled by the view, false otherwise.*/public boolean dispatchTouchEvent(MotionEvent event) {if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onTouchEvent(event, 0);}if (onFilterTouchEventForSecurity(event)) {//noinspection SimplifiableIfStatementListenerInfo li = mListenerInfo;if (li != null && li.mOnTouchListener != null && (mViewFlags &ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) {return true;}if (onTouchEvent(event)) {return true;}}if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);}return false;}挑选关键代码进行分析,可以看代码第16行,这里有几个条件,当几个条件都满足时该方法就返回true,当条件li.mOnTouchListener不为空时,通过在源码中查找,发现mOnTouchListener是在以下方法中进行设置的。/*** Register a callback to be invoked when a touch event is sent to this view.* @param l the touch listener to attach to this view*/public void setOnTouchListener(OnTouchListener l) {getListenerInfo().mOnTouchListener = l;}这个方法就已经很熟悉了,就是我们在MainActivity.java中为RTButton设置的onTouchListener,条件(mViewFlags & ENABLED_MASK) == ENABLED判断的是当前View是否是ENABLE的,默认都是ENABLE状态的。接着就是li.mOnTouchListener.onTouch(this, event)条件,这里调用了onTouch方法,该方法的调用就是我们在MainActivity.java中为RTButton设置的监听回调,如果该方法返回true,则整个条件都满足,dispatchTouchEvent就返回true,表示该事件就不继续向下分发了,因为已经被onTouch消费了。/*** Call this view"s OnClickListener, if it is defined. Performs all normal* actions associated with clicking: reporting accessibility event, playing* a sound, etc.** @return True there was an assigned OnClickListener that was called, false* otherwise is returned.*/public boolean performClick() {sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);ListenerInfo li = mListenerInfo;if (li != null && li.mOnClickListener != null) {playSoundEffect(SoundEffectConstants.CLICK);li.mOnClickListener.onClick(this);return true;}return false;}在if分支里可以看到执行了li.mOnClickListener.onClick(this);这句代码,这里就执行了我们为RTButton实现的onClick方法,所以,到目前为止,可以回答前一个“onClick是在哪里被调用的呢?”的问题了,onClick是在onTouchEvent中被执行的,并且,onClick要后于onTouch的执行。
可以看到,事件执行到dispatchTouchEvent方法就没有再继续往下分发了,这也验证了之前的说法,返回true时,不再继续往下分发,从之前分析过的Activity的dispatchTouchEvent源码中也可知,当返回true时,就没有去执行onTouchEvent方法了。
接着,将上述修改还原,让事件在Activity这继续往下分发,接着就分发到了RTButton,将RTButton的dispatchTouchEvent方法的返回值修改为true,重新编译运行并查看输出日志结果。

从结果可以看到,事件在RTButton的dispatchTouchEvent方法中就没有再继续往下分发了。接着将上述修改还原,将RTButton的onTouchEvent方法返回值修改为true,让其消费事件,根据之前的分析,onClick方法是在onTouchEvent方法中被调用的,事件在这被消费后将不会调用onClick方法了,编译运行,得到如下日志输出结果。

跟分析结果一样,onClick方法并没有被执行,因为事件在RTButton的onTouchEvent方法中被消费了。下图是整个事件传递的流程图。

到目前为止,Android中的事件拦截机制就分析完了。但这里我们只讨论了单布局结构下单控件的情况,如果是嵌套布局,那情况又是怎样的呢?接下来我们就在嵌套布局的情况下对Android的事件传递机制进行进一步的探究和分析。
Android嵌套布局事件传递
首先,新建一个类RTLayout继承于LinearLayout,同样重写dispatchTouchEvent和onTouchEvent方法,另外,还需要重写onInterceptTouchEvent方法,在文章开头介绍过,这个方法只有在ViewGroup和其子类中才存在,作用是控制是否需要拦截事件。这里不要和dispatchTouchEvent弄混淆了,后者是控制对事件的分发,并且后者要先执行。
那么,事件是先传递到View呢,还是先传递到ViewGroup的?通过下面的分析我们可以得出结论。首先,我们需要对工程代码进行一些修改。
RTLayout.java
public class RTLayout extends LinearLayout {public RTLayout(Context context, AttributeSet attrs) {super(context, attrs);}@Overridepublic boolean dispatchTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:System.out.println("RTLayout---dispatchTouchEvent---DOWN");break;case MotionEvent.ACTION_MOVE:System.out.println("RTLayout---dispatchTouchEvent---MOVE");break;case MotionEvent.ACTION_UP:System.out.println("RTLayout---dispatchTouchEvent---UP");break;default:break;}return super.dispatchTouchEvent(event);}@Overridepublic boolean onInterceptTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:System.out.println("RTLayout---onInterceptTouchEvent---DOWN");break;case MotionEvent.ACTION_MOVE:System.out.println("RTLayout---onInterceptTouchEvent---MOVE");break;case MotionEvent.ACTION_UP:System.out.println("RTLayout---onInterceptTouchEvent---UP");break;default:break;}return super.onInterceptTouchEvent(event);}@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:System.out.println("RTLayout---onTouchEvent---DOWN");break;case MotionEvent.ACTION_MOVE:System.out.println("RTLayout---onTouchEvent---MOVE");break;case MotionEvent.ACTION_UP:System.out.println("RTLayout---onTouchEvent---UP");break;default:break;}return super.onTouchEvent(event);}}同时,在布局文件中为RTButton添加一个父布局,指明为自定义的RTLayout,修改后的布局文件如下。<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent" ><com.ryantang.eventdispatchdemo.RTLayoutandroid:id="@+id/myLayout"android:layout_width="match_parent"android:layout_height="match_parent" ><com.ryantang.eventdispatchdemo.RTButtonandroid:id="@+id/btn"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="Button" /></com.ryantang.eventdispatchdemo.RTLayout></LinearLayout>最后,我们在Activity中也为RTLayout设置onTouch和onClick事件,在MainActivity中添加如下代码。
rtLayout.setOnTouchListener(new OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:System.out.println("RTLayout---onTouch---DOWN");break;case MotionEvent.ACTION_MOVE:System.out.println("RTLayout---onTouch---MOVE");break;case MotionEvent.ACTION_UP:System.out.println("RTLayout---onTouch---UP");break;default:break;}return false;}});rtLayout.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {System.out.println("RTLayout clicked!");}});代码修改完毕后,编译运行工程,同样,点击按钮,查看日志输出结果如下:
从日志输出结果我们可以看到,嵌套了RTLayout以后,事件传递的顺序变成了Activity->RTLayout->RTButton,这也就回答了前面提出的问题,Android中事件传递是从ViewGroup传递到View的,而不是反过来传递的。
从输出结果第三行可以看到,执行了RTLayout的onInterceptTouchEvent方法,该方法的作用就是判断是否需要拦截事件,我们到ViewGroup的源码中看看该方法的实现。
ViewGroup.java
public boolean onInterceptTouchEvent(MotionEvent ev) {return false;}该方法的实现很简单,只返回了一个false。那么这个方法是在哪被调用的呢,通过日志输出分析可知它是在RTLayout的dispatchTouchEvent执行后执行的,那我们就进到dispatchTouchEvent源码里面去看看。由于源码比较长,我将其中的关键部分截取出来做解释说明。// Check for interception.final boolean intercepted;if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;if (!disallowIntercept) {intercepted = onInterceptTouchEvent(ev);ev.setAction(action); // restore action in case it was changed} else {intercepted = false;}} else {// There are no touch targets and this action is not an initial down// so this view group continues to intercept touches.intercepted = true;}从这部分代码中可以看到onInterceptTouchEvent调用后返回值被赋值给intercepted,该变量控制了事件是否要向其子控件分发,所以它起到拦截的作用,如果onInterceptTouchEvent返回false则不拦截,如果返回true则拦截当前事件。我们现在将RTLayout中的该方法返回值修改为true,并重新编译运行,然后点击按钮,查看输出结果如下。
可以看到,我们明明点击的按钮,但输出结果显示RTLayout点击事件被执行了,再通过输出结果分析,对比上次的输出结果,发现本次的输出结果完全没有RTButton的信息,没错,由于onInterceptTouchEvent方法我们返回了true,在这里就将事件拦截了,所以他不会继续分发给RTButton了,反而交给自身的onTouchEvent方法执行了,理所当然,最后执行的就是RTLayout的点击事件了。
总结
以上我们对Android事件传递机制进行了分析,期间结合系统源码对事件传递过程中的处理情况进行了探究。通过单布局情况和嵌套布局情况下的事件传递和处理进行了分析,现总结如下:
Android中事件传递按照从上到下进行层级传递,事件处理从Activity开始到ViewGroup再到View。
事件传递方法包括dispatchTouchEvent、onTouchEvent、onInterceptTouchEvent,其中前两个是View和ViewGroup都有的,最后一个是只有ViewGroup才有的方法。这三个方法的作用分别是负责事件分发、事件处理、事件拦截。
onTouch事件要先于onClick事件执行,onTouch在事件分发方法dispatchTouchEvent中调用,而onClick在事件处理方法onTouchEvent中被调用,onTouchEvent要后于dispatchTouchEvent方法的调用。