android触摸事件分发

215 阅读4分钟

首先,我们需要先学习两个概念:

  1. 触摸事件

    触摸事件是android系统将硬件「检测到的手指与屏幕的接触」转变成的MotionEvent对象,从类型上划分常见的有三类,ACTION_DOWN(手指按在屏幕上的动作),ACTION_MOVE(手指在屏幕上移动的动作),ACTION_UP(手指在屏幕上抬起的动作),大多数情况下,我们每一次手指的操作,都能构成一个ACTION_DOWN ------> ACTION_MOVE ... ACTION_MOVE ------>ACTION_UP这样的闭环。

  2. 触摸事件的处理者

    主要就是布局中的View。

触摸事件的一般规律

我们首先讨论最简单的ViewGroup和View。android的布局是一个层层包裹的结构,触摸事件最开始是由外往内一层一层传递的。这里先讨论其中一层的ViewGroup的触摸事件处理规律,再扩散到所有,就很容易得到整个体系中触摸事件的处理规律。

下面是dispatchTouchEvent方法的官方注释

/**
 * Pass the touch screen motion event down to the target view, or this
 * view if it is the target.
 * 将触摸事件传递给目标view,如果当前view就是目标view,就传递给当前view
 *
 * @param event The motion event to be dispatched.
 * 参数是要被分发的触摸事件
 * 
 * @return True if the event was handled by the view, false otherwise.
 * 如果这个触摸事件被目标view处理掉了,就返回true,否则返回false
 */
public boolean dispatchTouchEvent(MotionEvent event) {
	//省略代码
}

下面是onInterceptTouchEvent方法的官方注释

/**
 * Implement this method to intercept all touch screen motion events.  This
 * allows you to watch events as they are dispatched to your children, and
 * take ownership of the current gesture at any point.
 * 实现这个方法来拦截所有的触摸事件。这个方法允许你监控所有分发给子view的触摸事件,
 * 并且可以在任何时间点来拦截并自己处理当前的触摸事件
 *
 * <p>Using this function takes some care, as it has a fairly complicated
 * interaction with {@link View#onTouchEvent(MotionEvent)
 * View.onTouchEvent(MotionEvent)}, and using it requires implementing
 * that method as well as this one in the correct way.  Events will be
 * received in the following order:
 * 使用这个功能需要当心,因为它与View的onTouchEvent方法之间存在相当复杂的交互关系,使用这个方法要求你能
 * 正确的实现这两个方法。触摸事件会以下面的顺序被接收
 *
 * <ol>
 * <li> You will receive the down event here.
 * <li> The down event will be handled either by a child of this view
 * group, or given to your own onTouchEvent() method to handle; this means
 * you should implement onTouchEvent() to return true, so you will
 * continue to see the rest of the gesture (instead of looking for
 * a parent view to handle it).  Also, by returning true from
 * onTouchEvent(), you will not receive any following
 * events in onInterceptTouchEvent() and all touch processing must
 * happen in onTouchEvent() like normal.
 * <li> For as long as you return false from this function, each following
 * event (up to and including the final up) will be delivered first here
 * and then to the target's onTouchEvent().
 * <li> If you return true from here, you will not receive any
 * following events: the target view will receive the same event but
 * with the action {@link MotionEvent#ACTION_CANCEL}, and all further
 * events will be delivered to your onTouchEvent() method and no longer
 * appear here.
 * </ol>
 * 
 *
 * @param ev The motion event being dispatched down the hierarchy.
 * @return Return true to steal motion events from the children and have
 * them dispatched to this ViewGroup through onTouchEvent().
 * The current target will receive an ACTION_CANCEL event, and no further
 * messages will be delivered here.
 */
public boolean onInterceptTouchEvent(MotionEvent ev) {}

下面是onTouchEvent方法

/**
 * Implement this method to handle touch screen motion events.
 * <p>
 * If this method is used to detect click actions, it is recommended that
 * the actions be performed by implementing and calling
 * {@link #performClick()}. This will ensure consistent system behavior,
 * including:
 * <ul>
 * <li>obeying click sound preferences
 * <li>dispatching OnClickListener calls
 * <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
 * accessibility features are enabled
 * </ul>
 *
 * @param event The motion event.
 * @return True if the event was handled, false otherwise.
 */
public boolean onTouchEvent(MotionEvent event) {}

我本人总结了一套触摸事件的传递规律(如果有不对的地方希望大家指正):

首先,我引入一个概念「目标布局」,它指的是一个完整的触摸事件(从手指按下,移动,到抬起)中处理了上一个触摸事件的布局。如果没有上一个事件(比如当前事件是按下事件)或者上一个事件没有被处理,则认为没有目标布局。

现在,我们开始梳理触摸事件的传递与处理过程。最开始,手指与屏幕交互产生触摸事件,首先进入activity的dispatchTouchEvent方法,然后进入最外层ViewGroup的dispatchTouchEvent方法,判断当前view是不是目标布局,如果是的,直接把触摸事件传递给自己的onTouchEvent,如果不是或者没有目标布局,调用onInterceptTouchEvent来决定是否拦截触摸事件,如果拦截,进入onTouchEvent,如果dispatchTouchEvent返回true,则自己处理触摸事件,否则将触摸事件传递回上一层;如果不拦截,触摸事件进入子布局的dispatchTouchEvent,重复刚刚说过的过程。