Android开发学习笔记——自定义View(三)View事件分发机制
MotionEvent
要了解View事件分发机制,首先,我们需要了解事件分发的对象MotionEvent,即被分发的事件,在Android的View事件体系中被分发的对象是用户触摸屏幕而产生的各种事件,主要包括有按下、滑动、抬起与取消。这些事件都被封装成了MotionEvent对象,如下表所示:
事件 | 触发场景 | 触发次数 |
---|---|---|
MotionEvent.ACTION_DOWN | 手指刚接触屏幕按下时 | 1次 |
MotionEvent.ACTION_MOVE | 手指在屏幕上滑动时 | 0次或多次 |
MotionEvent.ACTION_UP | 手指离开屏幕抬起时 | 0次或1次 |
MotionEvent.ACTION_CANCLE | 滑动超出控件边界时 | 0次或1次 |
按下、滑动、抬起和取消这几种事件组成了一个事件流。事件流以按下开始,中间可能有若干次滑动,最后以抬起或取消作为结束。在Android系统对事件分发的处理过程中,主要是对按下事件作分发,进而找到能够处理按下事件的组件。对于事件流中后续的事件(如滑动、抬起等),则直接分发给能够处理按下事件的组件。
View事件分发机制
View事件分发机制的本质就是将点击事件(MotionEvent)传递到某个具体的View并且进行处理的过程,也就是说事件分发过程其实就是MotionEvent传递的过程。因此,首先,我们需要知道事件分发是在哪些对象之间进行的。
分发事件的组件
分发事件的组件,即事件分发者包括Activity、View和ViewGroup,它们之间事件传递的顺序为Activity->ViewGroup->View,即在1个点击事件发生后,事件先传到Activity、再传到ViewGroup、最终再传到 View。如下图:
事件分发中的核心方法
在View的事件分发机制中,主要由三个核心方法协作完成,包括:dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent(),具体说明如下:
方法 | 说明 | 存在位置 |
---|---|---|
dispatchTouchEvent | 用于进行事件的分发;如果事件能够传递给当前的View,那么此方法一定会被调用,返回结果表示是否消耗当前事件,受当前View的onTouchEvent和子View的dispatchTouchEvent影响 | Activity、ViewGroup和View |
onInterceptTouchEvent | 用于判断是否拦截某个事件;在dispatchTouchEvent中被调用,如果当前View拦截了某个事件,那么在同一个事件序列中,该方法不会被再次调用,返回结果表示是否拦截当前事件 | 只存在于viewGroup |
onTouchEvent | 用于处理点击事件,在dispatchTouchEvent中被调用,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,该方法不会被当前View再次接收 | Activity、ViewGroup和View |
在View的事件分发机制中,核心就是由这三个方法来完成的,其大致流程可以用以下伪代码来表示:
public boolean dispatchTouchEvent(MotionEvent event){
boolean result = false;
if(onInterceptTouchEvent(event)){
//判断是否拦截事件
result = onTouchEvent(event);//拦截事件,调用onTouchEvent处理事件,返回是否消耗事件结果
} else {
result = child.dispathTouchEvent(event);//不拦截事件,将事件分发给子view,让子View处理事件,返回是否消耗事件
}
return result;//返回结果代表是否消耗事件
}
从上述伪代码中,我们可以大致了解到事件分发机制的流程,当点击事件产生时,其会被传递到跟ViewGroup,这时dispatchTouchEvent会被调用,如果其onInterceptTouchEvent方法返回true,就表示这个ViewGroup要拦截当前事件,接着事件就会交给其onTouchEvent方法处理,而如果onInterceptTouchEvent方法返回false,表示事件不被拦截,这时就会调用子view的dispathTouchEvent方法,当前事件就会被传递到子view去处理,直到事件被消耗。
事件分发过程
综上所述,View的事件分发流程为Activity->ViewGroup->View,也就是说,我们只需要分别掌握三者的事件分发过程即可了解整个事件分发的流程,接下来,我们将根据源码依次来了解三者的事件分发机制流程。
Activity的事件分发
当点击事件产生时,事件最先被传递给当前Activity,由Activity的dispatchTouchEvent来进行事件分发,其具体工作是由Activity内部的Window来完成的,Window会将事件传递给界面底层容器DecorView,代码如下:
/**
* 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) {
//事件序列从DOWN开始,都会被调用
onUserInteraction();//实现屏保功能,空方法
}
if (getWindow().superDispatchTouchEvent(ev)) {
//事件交给Window分发处理
return true;//事件被消耗
}
return onTouchEvent(ev);//事件未被消耗,自己处理
}
从上述代码,我们可以看到Activity会通过getWindow().superDispatchTouchEvent(ev)将事件交给Window进行分发处理,如果返回true说明事件被消耗整个事件分发结束,直接返回true;如果返回false,则调用onTouchEvent来处理事件。Window是一个抽象类,superDispatchTouchEvent也是一个抽象方法,其唯一的实现类为PhoneWindow,查看PhoneWindow中superDispatchTouchEvent方法的实现,如下:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);//将事件交给DecorView处理
}
PhoneWindow的superDispatchTouchEvent方法中,我们可以看到PhoneWindow将事件交给DecorView处理,DecorView是PhoneWindow的一个内部类,其继承自FrameLayout,是所有界面的父容器,在Activity中调用setContentView所设置的View就是其子View。我们再查看其superDispatchTouchEvent,如下:
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);//调用父类的dispatchTouchEvent
}
我们可以看到,其内部也只有一行代码,调用了其父类的dispatchTouchEvent,我们知道DecorView是继承自FrameLayout,即继承自ViewGroup,至此Activity的事件最终被分发给ViewGroup处理。
我们再回过头来看,当事件没有被Activity中任何一个View消耗掉时,Activity中的onTouchEvent方法是如何处理事件的,代码如下:
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
//事件是否发生Window边界外
finish();
return true;
}
return false;
}
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
final boolean isOutside =
event.getAction() == MotionEvent.ACTION_UP && isOutOfBounds(context, event)
|| event.getAction() == MotionEvent.ACTION_OUTSIDE;
if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) {
//对应边界外点击事件的判断
return true;
}
return false;
}
我们可以看到,当事件发生在Window边界外才会返回true,其它情况都返回false。
至此,Activity的事件分发就全部分析完毕,综上所述,我们可以发现,Activity的事件分发实际上就是通过Window将事件分发给DecorView,进而传递给ViewGroup,具体如下图:
ViewGroup的事件分发
通过PhoneWindow,Activity将事件传递到DecorView,调用ViewGroup的dispatchTouchEvent方法,至此,ViewGroup的事件分发机制开始。ViewGroup中的dispatchTouchEvent方法较长,我们分段分析其中的核心代码,如下: