转载请注明出处:http://blog.csdn.net/fishle123/article/details/50638795
在 Android UI 开发中,经常涉及touch(触摸)事件和手势。最经常使用的点击事件(OnClickListener)也与 touch 事件相关。因此,理解 touch 事件在 View 层级中的传递机制尤为重要。然而,dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent等一系列接口方法很容易让人混淆。
本文将介绍 touch 事件的一些基础知识,并通过分析 Android FrameWork 源码来深入理解 touch 事件的分发机制。
一、MotionEvent与事件分发相关的方法
1.MotionEvent
MotionEvent类封装了一个 Touch 事件的相关参数,我们通常所说的一个 Touch 事件,就是通过一个MotionEvent类的实例来描述。一个 MotionEvent 可以分为多种类型,即 ACTION_DOWN(按下)、ACTION_MOVE(移动)、ACTION_UP(抬起)和 ACTION_CANCEL(取消)等。
1)ACTION_DOWN:
一般来说,通常的 Touch 事件触发的流程都是 DOWN -->UP,或者 DOWN -->MOVE-->...-->MOVE--> UP。所以 ACTION_DOWN 事件通常都是一系列连续操作事件的起点,MOVE事件是否能够被触发取决于操作手势里面是否包含了移动的动作。
2)ACTION_MOVE:
当手指按下后在屏幕上移动时,就会产生 ACTION_MOVE 事件,并且通常会随着手指移动而连续产生很多个。
3)ACTION_UP:
UP 是一系列手势操作的结束点,程序会在收到 ACTION_UP 事件时做一些收尾性的工作,例如恢复 View 的点击状态,值得一提的是,View 的 onClick 方法就是在 ACTION_UP 时加以判断满足其他条件之后被触发的。
4)ACTION_CANCEL:
CANCEL 事件不是由用户触发的,而是系统经过逻辑判断后对某个 View 发送“取消”消息时产生的。收到 CANCEL 事件时,View 应该负责将自己的状态恢复。
2.事件分发方法
public boolean dispatchTouchEvent(MotionEvent ev):
事件由上一层的 View 传递到下一层 View 的过程称为事件分发。dispatchTouchEvent 方法负责事件分发。Activity、ViewGroup、View 类中都定义了该方法,所以它们都具有事件分发的能力。
Activity.dispatchTouchEvent 实际上是调用了 DecorView 的 dispatchTouchEvent 方法,而 DecorView 实际上是一个 FrameLayout,因此,Activity 的 dispatchTouchEvent 最终也是调用到了 ViewGroup 的 dispatchTouchEvent 方法。
另外,由于 View 没有管理子 View 的能力,所以 View.dispatchTouchEvent 方法实际上不是用来向下分发事件,而是将事件分发给自己,调用了自己的事件响应方法去响应事件。
3.事件拦截
public boolean onInterceptTouchEvent(MotonEvent event)
事件在 ViewGroup 的分发过程中,ViewGroup 可以决定是否拦截事件而不对子 View 分发。该方法的返回值决定是否需要拦截的,返回 true 表示拦截,false 表示不拦截。该方法只定义在 ViewGroup 类中.
4.事件响应方法
public boolean onTouchEvent(MotionEvent event):
该方法负责响应事件,并且返回一个 boolean 型,表示是否消费掉事件,返回 true 表示消费,false 表示不消费。Activity、View、ViewGroup 都有这个方法,所以它们都具有事件响应的能力,并且通过返回值来表示事件是否已经消费。如果子View没有消费掉Touch事件,那么会传递给上一层的View来处理。
消息分发流程,从上到下,从父到子:Activity->ViewGroup1->ViewGroup1的子ViewGroup2->…->Target View
消息响应流程,从下到上,从子到父:Target View->…->ViewGroup1的子ViewGroup2->ViewGroup1->Activity
二、View 中 Touch 事件的分发逻辑
先来看 一下View.dispatchTouchEvent 的源码:
/**
* 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) {
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
//只要view设置了OnTouchListerner并且enable了,那么就调用OnTouchListener.onTouch来处理
result = true;
}
//如果view没有设置OnTouchListener或者OnTouchListener.onTouch返回false(表明没有消费该Touch事件),那么调用View的onTouchEvent来处理
if (!result && onTouchEve