Touch事件传递机制,其实说起来还是比较复杂的,所涉及的内容和细节也都比较多。为了方便理解,本文将由浅入深的进行讲解。
首先要知道我们对于屏幕的所有操作,包括点击、放开、滑动,以及由这些基本操作组成的放大、缩小、旋转等操作全部是被封装在MotionEvent对象中进行操作的。我们需要通过getAction()判断是何种事件。这些事件包括如下6种:
ACTION_DOWN: 第一个点按下时触发
ACTION_UP: 屏幕上唯一的一个点抬起时触发
ACTION_MOVE: 当屏幕上的点移动时触发。
ACTION_POINTER_DOWN: 当屏幕上已经有一点按住,再按下其他点便会触发该事件。
ACTION_POINTER_UP: 当屏幕上有多个点被按住,松开其中的点时触发(非最后一个点,最后一个点触发ACTION_UP)。
ACTION_CANCEL:当前的手势被释放时会触发,当前手势指的是从ACTION_DOWN开始以及后面一系列的ACTION_MOVE、ACTION_POINTER_DOWN、ACTION_POINTER_UP操作。
到这里我们就可以写出屏幕触摸事件相应的代码了:
@Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: // 第一个点按下去的相关处理 break; case MotionEvent.ACTION_MOVE: // 手指在屏幕上移动相关处理 break; case MotionEvent.ACTION_UP: // 屏幕上最后一个点离开屏幕时相关处理 break; } return super.onTouchEvent(ev); }
只要把握这六种事件就可以了,是不是很简单呢?但是Touch事件并非这么简单,到这里还有很多问题没有搞清楚。Touch事件的事件源,处理者是谁?处理的具体方法有哪些?具体的流程如何?下面还请听详细分解。
Touch事件的处理分为三个层面:Activity,ViewGroup,View。我们所说的Touch事件传递便是在这三个层面之间进行传递的。而事件的传递,依靠的是三个方法dispatchTouchEvent()----事件分派,onInterceptTouchEvent()----事件拦截,onTouchEvent()----事件处理。我们上面看到的示例代码中onTouchEvent()便是我们实际处理操作的方法。而至于事件是如何传递的,上面我们并不清楚。下表给出这三个层次具体的实例类和和这三个方法之间的关系。
层次 |
实例类 |
方法 |
Activity |
|
dispatchTouchEvent, onTouchEvent |
ViewGroup |
RelativeLayout, FrameLayout, LinearLayout, AbsoluteLayout, ListView,ScrollView... |
dispatchTouchEvent, onInterceptTouchEvent, onTouchEvent |
View |
Button, EditText, TextView, ImageView |
dispatchTouchEvent, onTouchEvent |
表一 三个层次及其具体实例类和三个方法之间的对应关系
下面将通过一个示例来表现Touch事件究竟是如何在这三个层面之间进行传递的。我们拿下面这张图来作为Touch事件传递的层次图。
图一 Touch事件传递涉及层次图
所有的Touch事件一定都是从ACTION_DOWN这个事件开始的,那么当你按下屏幕那一刻开始,也就是触发了ACTION_DOWN这个事件。Touch事件传递便开始工作了。以上面这张图为例。
当ACTION_DOWN被触发,系统会调用Activity的dispatchTouchEvent方法,对该事件进行分发,然后将事件分发到RelativeLayout的dispatchTouchEvent方法进行处理。RelativeLayout会调用它的onInterceptTouchEvent方法判断是否要拦截下这个事件,然后自己处理。如果返回true那个执行RelativeLayout的onTouchEvent方法,如果返回false那么这条传递链就继续向下延伸到RelativeLayout的子View –TextView的dispatchTouchEvent,由于TextView已经不能够有子View了,不能够再向下传递,dispatchTouchEvent再将事件分发到TextView的onTouchEvent方法,onTouchEvent返回true或者false:
1) 如果onTouchEvent返回true,表示TextView能够处理该事件,那么事件就不再传递下去,后续的事件(ACTION_MOVE, ACTION_POINTER_DOWN, ACTION_POINTER_UP,ACTION_UP)都会按照同样的传递过程进行传递,最后交给TextView的onTouchEvent方法进行处理。
2) 如果onTouchEvent返回false,表示TextView不能够处理该事件,这时事件就会发生回传,传递给其父View– RelativeLayout的onTouchEvent方法,如果onTouchEvent方法返回true,表示RelativeLayout可以处理该事件,那么后续的事件传递到RelativeLayout的onInterceptTouchEvent方法的时候就会拦截下来直接由RelativeLayout的onTouchEvent进行处理;如果onTouchEvent方法返回false,那么事件继续进行上传到Activity的OnTouchEvent,如果返回true由它进行处理,如果返回false,由于它已经是最顶层了,事件传递到此为止,Touch事件无效。
说了这么多,不如来一幅生动形象的图来描述整个的Touch事件传递过程吧。
上面两幅图分别表现的是1)和2)的事件传递图,实线表示ACTION_DOWN事件的传递,虚线表示后续其它事件的传递。第一张图中TextView的onTouchEvent处理该事件,那么第二次仍然按照原路传递到TextView由它进行处理;第二张图中TextView的onTouchEvent不能处理该事件,那么就回传到RelativeLayout的onTouchEvent进行处理,可以处理便不进行回传,后续其它事件再传递,传递到RelativeLayout就由它来进行处理,不需要再向下传递。
到这里Touch事件传递机制的说明就算完成了。但Touch事件仍有很多让人疑惑不解的, 比如ACTION_MOVE拖动的方向距离是如何实现的, ACTION_POINTER_UP, ACTION_POINTER_DOWN有是如何实现的。下面在对它们进行简要的讲解。
ACTION_MOVE拖动方向距离是通过MotionEvent的getX() getY(), getRawX()getRawY 这四个方法得到的。这里值得一提的是前面两个方法是得到的是相对于当前Widget左上角的X, Y坐标,而后面两个方法得到的是以手机屏幕左上角为原点的X, Y 坐标。既然得到了当前坐标那么拖动的距离方向就很好计算了,通过当前ACTION_MOVE事件得到的X,Y坐标减去上一个事件(可能是ACTION_DOWN也可能是ACTION_MOVE)得到的X, Y坐标就可以得知拖动的方向距离了。
而ACTION_POINTER_UP,ACTION_POINTER_DOWN的实现则是通过方法getX(pointerIndex)实现的,pointerIndex表示的就是屏幕上面的点,这样我们就可以知道屏幕上的每个点的当前坐标了。
另外你有没有发现,Touch事件传递和DOM事件捕获和事件冒泡有着异曲同工之妙。