Android事件传递机制
在Android中,事件主要包括点按、长按、拖拽、滑动等,点按又包括单击和双击,另外还包括单指操作和多指操作。所有这些都构成了Android中的事件响应。总的来说,所有的事件都由如下三个部分作为基础:
- 按下(ACTION_DOWN)
- 移动(ACTION_MOVE)
- 抬起(ACTION_UP)
所有的操作事件首先必须执行的是按下操作(ACTIONDOWN),之后所有的操作都是以按下操作作为前提,当按下操作完成后,接下来可能是一段移动(ACTIONMOVE)然后抬起(ACTION_UP),或者是按下操作执行完成后没有移动就直接抬起。这一系列的动作在Android中都可以进行控制。
我们知道,所有的事件操作都发生在触摸屏上,而在屏幕上与我们交互的就是各种各样的视图组件(View),在Android中,所有的视图都继承于 View,另外通过各种布局组件(ViewGroup)来对View进行布局,ViewGroup也继承于View。所有的UI控件例如Button、 TextView都是继承于View,而所有的布局控件例如RelativeLayout、容器控件例如ListView都是继承于ViewGroup。 所以,我们的事件操作主要就是发生在Activity,View和ViewGroup之间,那么View和ViewGroup中主要有哪些方法来对这些事件进行响应呢?记住 如下3个方法,我们通过查看View和ViewGroup的源码可以看到:
Activity.java
1 public boolean dispatchTouchEvent(MotionEvent event) 2 public boolean onTouchEvent(MotionEvent event)
View.java
1 public boolean dispatchTouchEvent(MotionEvent event) 2 public boolean onTouchEvent(MotionEvent event)
ViewGroup.java
1 public boolean dispatchTouchEvent(MotionEvent event) 2 public boolean onTouchEvent(MotionEvent event) 3 public boolean onInterceptTouchEvent(MotionEvent event)
在View和ViewGroup中都存在dispatchTouchEvent和onTouchEvent方法,但是在ViewGroup中还有一 个onInterceptTouchEvent方法,那这些方法都是干嘛的呢?别急,我们先看看他们的返回值。这些方法的返回值全部都是boolean
型, 为什么是boolean型呢,看看本文的标题,“事件传递”,传递的过程就是一个接一个,那到了某一个点后是否要继续往下传递呢?你发现了吗,“是否”二 字就决定了这些方法应该用boolean来作为返回值。没错,这些方法都返回true或者是false。在Android中,所有的事件都是从开始经过传 递到完成事件的消费,这些方法的返回值就决定了某一事件是否是继续往下传,还是被拦截了,或是被消费了。
接下来就是这些方法的参数,都接受了一个MotionEvent
类型的参数,MotionEvent继承于InputEvent,用于标记各种动作事件。之前提到的ACTIONDOWN、ACTIONMOVE、ACTION_UP都是MotinEvent中定义的常量。我们通过MotionEvent传进来的事件类型来判断接收的是哪一种类型的事件。到现在,这三个方法的返回值和参数你应该都明白了,接下来就解释一下这三个方法分别在什么时候处理事件。
- 事件分发:public boolean dispatchTouchEvent(MotionEvent ev)
当有监听到事件时,首先由Activity的捕获到,进入事件分发处理流程。无论是Activity还是View,如前文所说,事件分发自身也具有消费能力,
如果事件分发返回true,表示改事件在本层不再进行分发且已经在事件分发自身中被消费了。至此,事件已经完结。如果你不想Activity中的任何控件具有任何的事件消费能力,
最简答的方法可以重写此Activity的dispatchTouchEvent方法,直接返回true就ok。
如果事件分发返回 false,表明事件在本层不再继续进行分发,并交由上层控件的onTouchEvent方法进行消费。
当然了,如果本层控件已经是Activity,那么事件将被系统消费或处理。
如果事件分发返回系统默认的 super.dispatchTouchEvent(ev),事件将分发给本层的事件拦截onInterceptTouchEvent 方法进行处理
(如果本层控件是Activity,由于其没有事件拦截,因此将直接将事件传递到子View,并交给子View的事件分发进行处理)。
- 事件拦截:public boolean onInterceptTouchEvent(MotionEvent ev)
如果 onInterceptTouchEvent 返回 true,则表示将事件进行拦截,并将拦截到的事件交由本层控件 的 onTouchEvent 进行处理;
如果返回结果是false;则表示不对事件进行拦截,事件得以成功分发到子View。并由子View的dispatchTouchEvent进行处理。
如果返回super.onInterceptTouchEvent(ev),事件默认不会被拦截,交由子View的dispatchTouchEvent进行处理。
- 事件响应:public boolean onTouchEvent(MotionEvent ev)
如果onTouchEvent返回true,表示onTouchEvent处理完事件后消费了此次事件。此时事件终结,将不会进行后续的冒泡。
如果onTouchEvent返回false,事件在onTouchEvent中处理后继续向上层View冒泡,且有上层View的onTouchEvent进行处理。
如果返回super.onTouchEvent(ev),则默认处理的逻辑和返回false时相同。
总结:从以上过程中可以看出,dispatchTouchEvent无论返回true还是false,事件都不再进行分发,
只有当其返回super.dispatchTouchEvent(ev),才表明其具有向下层分发的愿望,
但是是否能够分发成功,则需要经过事件拦截onInterceptTouchEvent的审核。事件是否具有冒泡特是由onTouchEvent的返回值决定的。
到目前为止,Android中事件的构成以及事件处理方法的作用你应该比较清楚了,接下来我们就通过一个Demo来实际体验实验一下。
Android事件处理
首先在Eclipse新建一个工程,并新建一个类RTButton继承Button,用来实现我们对按钮事件的跟踪。
RTButton.java
1 public class RTButton extends Button { 2 public RTButton(Context context, AttributeSet attrs) { 3 super(context, attrs); 4 } 5 6 @Override 7 public boolean dispatchTouchEvent(MotionEvent event) { 8 switch (event.getAction()) { 9 case MotionEvent.ACTION_DOWN: 10 System.out.println("RTButton---dispatchTouchEvent---DOWN"); 11 break; 12 case MotionEvent.ACTION_MOVE: 13 System.out.println("RTButton---dispatchTouchEvent---MOVE"); 14 break; 15 case MotionEvent.ACTION_UP: 16 System.out.println("RTButton---dispatchTouchEvent---UP"); 17 break; 18 default: 19 break; 20 } 21 return super.dispatchTouchEvent(event); 22 } 23 24 @Override 25 public boolean onTouchEvent(MotionEvent event) { 26 switch (event.getAction()) { 27 case MotionEvent.ACTION_DOWN: 28 System.out.println("RTButton---onTouchEvent---DOWN"); 29 break; 30 case MotionEvent.ACTION_MOVE: 31 System.out.println("RTButton---onTouchEvent---MOVE"); 32 break; 33 case MotionEvent.ACTION_UP: 34 System.out.println("RTButton---onTouchEvent---UP"); 35 break; 36 default: 37 break; 38 } 39 return super.onTouchEvent(event); 40 } 41 }
在RTButton中我重写了dispatchTouchEvent和onTouchEvent方法,并获取了MotionEvent各个事件状 态,打印输出了每一个状态下的信息。然后在activity_main.xml中直接在根布局下放入自定义的按钮RTButton。
activity_main.xml
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:id="@+id/myLayout" 4 android:layout_width="match_parent" 5 android:layout_height="match_parent" > 6 7 <com.ryantang.eventdispatchdemo.RTButton 8 android:id="@+id/btn" 9 android:layout_width="match_parent" 10 android:layout_height="wrap_content" 11 android:text="Button"/> 12 13 </LinearLayout>
接下来在Activity中为RTButton设置onTouch和onClick的监听器来跟踪事件传递的过程,另外,Activity中也有一个dispatchTouchEvent方法和一个onTouchEvent方法,我们也重写他们并输出打印信息。
MainActivity.java
1 public class MainActivity extends Activity { 2 private RTButton button; 3 4 @Override 5 protected void onCreate(Bundle savedInstanceState) { 6 super.onCreate(savedInstanceState); 7 setContentView(R.layout.activity_main); 8 button = (RTButton)this.findViewById(R.id.btn); 9 button.setOnTouchListener(new OnTouchListener() { 10 11 @Override 12 public boolean onTouch(View v, MotionEvent event) { 13 switch (event.getAction()) { 14 case MotionEvent.ACTION_DOWN: 15 System.out.println("RTButton---onTouch---DOWN"); 16 break; 17 case MotionEvent.ACTION_MOVE: 18 System.out.println("RTButton---onTouch---MOVE"); 19 break; 20 case MotionEvent.ACTION_UP: 21 System.out.println("RTButton---onTouch---UP"); 22 break; 23 default: 24 break; 25 } 26 return false; 27 } 28 }); 29 30 button.setOnClickListener(new OnClickListener() { 31 32 @Override 33 public void onClick(View v) { 34 System.out.println("RTButton clicked!"); 35 } 36 }); 37 38 } 39 40 @Override 41 public boolean dispatchTouchEvent(MotionEvent event) { 42 switch (event.getAction()) { 43 case MotionEvent.ACTION_DOWN: 44 System.out.println("Activity---dispatchTouchEvent---DOWN"); 45 break; 46 case MotionEvent.ACTION_MOVE: 47 System.out.println("Activity---dispatchTouchEvent---MOVE"); 48 break; 49 case MotionEvent.ACTION_UP: 50 System.out.println("Activity---dispatchTouchEvent---UP"); 51 break; 52 default: 53 break; 54 } 55 return super.dispatchTouchEvent(event); 56 } 57 58 @Override 59 public boolean onTouchEvent(MotionEvent event) { 60 switch (event.getAction()) { 61 case MotionEvent.ACTION_DOWN: 62 System.out.println("Activity---onTouchEvent---DOWN"); 63 break; 64 case MotionEvent.ACTION_MOVE: 65 System.out.println("Activity---onTouchEvent---MOVE"); 66 break; 67 case MotionEvent.ACTION_UP: 68 System.out.println("Activity---onTouchEvent---UP"); 69 break; 70 default: 71 break; 72 } 73 return super.onTouchEvent(event); 74 } 75 }
代码部分已经完成了,接下来运行工程,并点击按钮,查看日志输出信息,我们可以看到如下结果:
通过日志输出可以看到,首先执行了Activity的dispatchTouchEvent方法进行事件分发,在MainActivity.java
代码第55行,dispatchTouchEvent方法的返回值是super.dispatchTouchEvent(event),因此调用了父类方法,我们进入Activity.java
的源码中看看具体实现。
Activity.java
1 /** 2 * Called to process touch screen events. You can override this to 3 * intercept all touch screen events before they are dispatched to the 4 * window. Be sure to call this implementation for touch screen events 5 * that should be handled normally. 6 * 7 * @param ev The touch screen event. 8 * 9 * @return boolean Return true if this event was consumed. 10 */ 11 public boolean dispatchTouchEvent(MotionEvent ev) { 12 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 13 onUserInteraction(); 14 } 15 if (getWindow().superDispatchTouchEvent(ev)) { 16 return true; 17 } 18 return onTouchEvent(ev); 19 }
从源码中可以看到,dispatchTouchEvent方法只处理了ACTIONDOWN事件,前面提到过,所有的事件都是以按下为起点的,所以,Android认为当ACTIONDOWN 事件没有执行时,后面的事件都是没有意义的,所以这里首先判断ACTION_DOWN事件。如果事件成立,则调用了onUserInteraction方 法,该方法可以在Activity中被重写,在事件被分发前会调用该方法。该方法的返回值是void型,不会对事件传递结果造成影响,接着会判断 getWindow().superDispatchTouchEvent(ev)的执行结果,看看它的源码:
Activity.java
1 /** 2 * Used by custom windows, such as Dialog, to pass the touch screen event 3 * further down the view hierarchy. Application developers should 4 * not need to implement or call this. 5 * 6 */ 7 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中的源码是如何处理的。
View.java
1 /** 2 * Pass the touch screen motion event down to the target view, or this 3 * view if it is the target. 4 * 5 * @param event The motion event to be dispatched. 6 * @return True if the event was handled by the view, false otherwise. 7 */ 8 public boolean dispatchTouchEvent(MotionEvent event) { 9 if (mInputEventConsistencyVerifier != null) { 10 mInputEventConsistencyVerifier.onTouchEvent(event, 0); 11 } 12 13 if (onFilterTouchEventForSecurity(event)) { 14 //noinspection SimplifiableIfStatement 15 ListenerInfo li = mListenerInfo; 16 if (li != null && li.mOnTouchListener != null && (mViewFlags & 17 ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { 18 return true; 19 } 20 21 if (onTouchEvent(event)) { 22 return true; 23 } 24 } 25 26 if (mInputEventConsistencyVerifier != null) { 27 mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); 28 } 29 return false; 30 }
挑选关键代码进行分析,可以看代码第16行,这里有几个条件,当几个条件都满足时该方法就返回true,当条件li.mOnTouchListener不为空时,通过在源码中查找,发现mOnTouchListener是在以下方法中进行设置的。
View.java
1 /** 2 * Register a callback to be invoked when a touch event is sent to this view. 3 * @param l the touch listener to attach to this view 4 */ 5 public void setOnTouchListener(OnTouchListener l) { 6 getListenerInfo().mOnTouchListener = l; 7 }
这个方法就已经很熟悉了,就是我们在MainActivity.java
中为RTButton设置的 onTouchListener,条件(mViewFlags & ENABLED_MASK) == ENABLED判断的是当前View是否是ENABLE的,默认都是ENABLE状态的。接着就是 li.mOnTouchListener.onTouch(this, event)条件,这里调用了onTouch方法,该方法的调用就是我们在MainActivity.java
中为RTButton设置的监听回调,如果该方法返回true,则整个条件都满足,dispatchTouchEvent就返回true,表示该事件就不继续向下分发了,因为已经被onTouch消费了。
如果onTouch返回的是false,则这个判断条件不成立,接着执行onTouchEvent(event)方法进行判断,如果该方法返回 true,表示事件被onTouchEvent处理了,则整个dispatchTouchEvent就返回true。到这里,我们就可以回答之前提出的 “为什么先执行onTouch方法”的问题了。到目前为止,ACTIONDOWN的事件经过了从Activity到RTButton的分发,然后经过onTouch和onTouchEvent的处理,最终,ACTIONDOWN事件交给了RTButton得onTouchEvent进行处理。
当我们的手(我这里用的Genymotion然后用鼠标进行的操作,用手的话可能会执行一些ACTIONMOVE操作)从屏幕抬起时,会发生ACTIONUP事件。从之前输出的日志信心中可以看到,ACTIONUP事件同样从Activity开始到RTButton进行分发和处理,最后,由于我们注册了onClick事件,当onTouchEvent执行完毕后,就调用了onClick事件,那么onClick是在哪里被调用的呢?继续回到View.java
的源代码中寻找。由于onTouchEvent在View.java
中的源码比较长,这里就不贴出来了,感兴趣的可以自己去研究一下,通过源码阅读,我们在ACTIONUP的处理分支中可以看到一个performClick()
方法,从这个方法的源码中可以看到执行了哪些操作。
View.java
1 /** 2 * Call this view's OnClickListener, if it is defined. Performs all normal 3 * actions associated with clicking: reporting accessibility event, playing 4 * a sound, etc. 5 * 6 * @return True there was an assigned OnClickListener that was called, false 7 * otherwise is returned. 8 */ 9 public boolean performClick() { 10 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); 11 12 ListenerInfo li = mListenerInfo; 13 if (li != null && li.mOnClickListener != null) { 14 playSoundEffect(SoundEffectConstants.CLICK); 15 li.mOnClickListener.onClick(this); 16 return true; 17 } 18 19 return false; 20 }
在if分支里可以看到执行了li.mOnClickListener.onClick(this);这句代码,这里就执行了我们为RTButton 实现的onClick方法,所以,到目前为止,可以回答前一个“onClick是在哪里被调用的呢?”的问题了,onClick是在 onTouchEvent中被执行的,并且,onClick要后于onTouch的执行。
到此,点击按钮的事件传递就结束了,我们结合源代码窥探了其中的执行细节,如果我们修改各个事件控制方法的返回值又会发生什么情况呢,带着这个问题,进入下一节的讨论。
Android事件拦截
从上一节分析中,我们知道了在Android中存在哪些事件类型,事件的传递过程以及在源码中对应哪些处理方法。我们可以知道在Android中,事件是通过层级传递的,一次事件传递对应一个完整的层级关系,例如上节中分析的ACTIONDOWN事件从Activity传递到RTButton,ACTIONUP事件也同样。结合源码分析各个事件处理的方法,也可以明确看到事件的处理流程。
之前提过,所有事件处理方法的返回值都是boolean类型的,现在我们来修改这个返回值,首先从Activity开始,根据之前的日志输出结果, 首先执行的是Activity的dispatchTouchEvent方法,现在将之前的返回值 super.dispatchTouchEvent(event)修改为true,然后重新编译运行并点击按钮,看到如下的日志输出结果。
可以看到,事件执行到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
1 public class RTLayout extends LinearLayout { 2 public RTLayout(Context context, AttributeSet attrs) { 3 super(context, attrs); 4 } 5 6 @Override 7 public boolean dispatchTouchEvent(MotionEvent event) { 8 switch (event.getAction()) { 9 case MotionEvent.ACTION_DOWN: 10 System.out.println("RTLayout---dispatchTouchEvent---DOWN"); 11 break; 12 case MotionEvent.ACTION_MOVE: 13 System.out.println("RTLayout---dispatchTouchEvent---MOVE"); 14 break; 15 case MotionEvent.ACTION_UP: 16 System.out.println("RTLayout---dispatchTouchEvent---UP"); 17 break; 18 default: 19 break; 20 } 21 return super.dispatchTouchEvent(event); 22 } 23 24 @Override 25 public boolean onInterceptTouchEvent(MotionEvent event) { 26 switch (event.getAction()) { 27 case MotionEvent.ACTION_DOWN: 28 System.out.println("RTLayout---onInterceptTouchEvent---DOWN"); 29 break; 30 case MotionEvent.ACTION_MOVE: 31 System.out.println("RTLayout---onInterceptTouchEvent---MOVE"); 32 break; 33 case MotionEvent.ACTION_UP: 34 System.out.println("RTLayout---onInterceptTouchEvent---UP"); 35 break; 36 default: 37 break; 38 } 39 return super.onInterceptTouchEvent(event); 40 } 41 42 @Override 43 public boolean onTouchEvent(MotionEvent event) { 44 switch (event.getAction()) { 45 case MotionEvent.ACTION_DOWN: 46 System.out.println("RTLayout---onTouchEvent---DOWN"); 47 break; 48 case MotionEvent.ACTION_MOVE: 49 System.out.println("RTLayout---onTouchEvent---MOVE"); 50 break; 51 case MotionEvent.ACTION_UP: 52 System.out.println("RTLayout---onTouchEvent---UP"); 53 break; 54 default: 55 break; 56 } 57 return super.onTouchEvent(event); 58 } 59 }
同时,在布局文件中为RTButton添加一个父布局,指明为自定义的RTLayout,修改后的布局文件如下。
activity_main.xml
<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.RTLayout 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" /> </com.ryantang.eventdispatchdemo.RTLayout> </LinearLayout>
最后,我们在Activity中也为RTLayout设置onTouch和onClick事件,在MainActivity中添加如下代码。
MainActivity.java
1 rtLayout.setOnTouchListener(new OnTouchListener() { 2 3 @Override 4 public boolean onTouch(View v, MotionEvent event) { 5 switch (event.getAction()) { 6 case MotionEvent.ACTION_DOWN: 7 System.out.println("RTLayout---onTouch---DOWN"); 8 break; 9 case MotionEvent.ACTION_MOVE: 10 System.out.println("RTLayout---onTouch---MOVE"); 11 break; 12 case MotionEvent.ACTION_UP: 13 System.out.println("RTLayout---onTouch---UP"); 14 break; 15 default: 16 break; 17 } 18 return false; 19 } 20 }); 21 22 rtLayout.setOnClickListener(new OnClickListener() { 23 24 @Override 25 public void onClick(View v) { 26 System.out.println("RTLayout clicked!"); 27 } 28 });
代码修改完毕后,编译运行工程,同样,点击按钮,查看日志输出结果如下:
从日志输出结果我们可以看到,嵌套了RTLayout以后,事件传递的顺序变成了 Activity->RTLayout->RTButton,这也就回答了前面提出的问题,Android中事件传递是从ViewGroup 传递到View的,而不是反过来传递的。
从输出结果第三行可以看到,执行了RTLayout的onInterceptTouchEvent方法,该方法的作用就是判断是否需要拦截事件,我们到ViewGroup的源码中看看该方法的实现。
ViewGroup.java
1 public boolean onInterceptTouchEvent(MotionEvent ev) { 2 return false; 3 }
该方法的实现很简单,只返回了一个false。那么这个方法是在哪被调用的呢,通过日志输出分析可知它是在RTLayout的 dispatchTouchEvent执行后执行的,那我们就进到dispatchTouchEvent源码里面去看看。由于源码比较长,我将其中的关键 部分截取出来做解释说明。
ViewGroup.java
1 // Check for interception. 2 final boolean intercepted; 3 if (actionMasked == MotionEvent.ACTION_DOWN 4 || mFirstTouchTarget != null) { 5 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; 6 if (!disallowIntercept) { 7 intercepted = onInterceptTouchEvent(ev); 8 ev.setAction(action); // restore action in case it was changed 9 } else { 10 intercepted = false; 11 } 12 } else { 13 // There are no touch targets and this action is not an initial down 14 // so this view group continues to intercept touches. 15 intercepted = true; 16 }
从这部分代码中可以看到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方法的调用。