Touch 事件分发机制

2015-09-22  本文已影响807人  xiazdong

原文:http://xiazdong.me/2015/09/19/touch-dispatch-mechanism/

前言

Touch 事件分发机制是面试中非常常见的问题,也是非常重要的问题。网上有很多关于这方面的文章,但是感觉写的不是特别清晰易懂。

基本概念

Touch 事件分发机制分发的是 MotionEvent 对象,取值如下:

一个事件序列包含 ACTION_DOWN->ACTION_MOVE->...->ACTION_MOVE->ACTION_UP,即用户触摸屏幕,移动一些距离,然后抬起。

  1. 下面说的 view "处理" 了某个事件,表示 view 调用了 onTouchEvent()。
  2. 下面说的 view "消费" 了某个事件,表示 view 调用了 onTouchEvent() 并返回 true。因此某个 view 可以处理但不消费某个事件。

Touch 事件分发机制涉及三个方法:

ViewGroup 的 dispatchTouchEvent(), onInterceptTouchEvent(), onTouchEvent() 的基本关系如下:

public boolean dispatchTouchEvent(MotionEvent ev){
    boolean consume = false;
    if(onInterceptTouchEvent(ev)){  //是否拦截
        consume = onTouchEvent(ev); //如果拦截,则自己处理
    }
    else{  //如果没拦截,则事件分发给孩子
        consume = child.dispatchTouchEvent(ev);
    }
    return consume;
}

上面的代码只是基本概括了整个事件分发的核心流程,具体实现细节会在下面介绍。

总体分发流程

当用户发起触摸事件后,首先触摸事件从 Activity 的 dispatchTouchEvent() 开始,该方法实现如下:

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

解释:

View Touch 事件分发

因为 ViewGroup 也是继承自 View,因此此处分两种情况讨论。

View 的 dispatchTouchEvent() 实现如下:

public boolean dispatchTouchEvent(MotionEvent event) 
{
    if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
            && li.mOnTouchListener.onTouch(this, event)) 
    {
        return true;
    }
    if (onTouchEvent(event)) 
    {
        return true;
    }
    return false;
}

从上面看出:

接着我们看看 onTouchEvent() 的实现:

public boolean onTouchEvent(MotionEvent event) {
    if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                performClick();   //执行 mClickListener.onClick() 方法
                break;
            case MotionEvent.ACTION_DOWN:
                ...
                break;
            case MotionEvent.ACTION_MOVE:
                ...
                break;
        }
        return true;
    }
    return false;
}

从上面可以看出:

ViewGroup Touch 事件分发

ViewGroup 的 dispatchTouchEvent() 比较复杂,下面我们分析一下。

public boolean dispatchTouchEvent(MotionEvent ev) {
    boolean handled = false;
    // 1、如果是 ACTION_DOWN 动作,则清除标志位。
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        cancelAndClearTouchTargets(ev);
        resetTouchState();  // 清除 FLAG_DISALLOW_INTERCEPT 标记位
    }

    // 2、判断是否要拦截该事件
    final boolean intercepted;
    if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null){
        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        if (!disallowIntercept) {
            intercepted = onInterceptTouchEvent(ev);
            ev.setAction(action);
        } else {
            intercepted = false;
        }
    }
    else{
        intercepted = true;
    }

    // 3、如果不拦截,则传给子 View
    TouchTarget newTouchTarget = null;
    boolean alreadyDispatchedToNewTouchTarget = false;
    if (!canceled && !intercepted) {
        if (actionMasked == MotionEvent.ACTION_DOWN){
              final View[] children = mChildren;
            for (int i = childrenCount - 1; i >= 0; i--) {
                final View child = children[childIndex];
                //如果该子 View 不在触摸范围内,则略过
                if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {
                    continue;
                }
                //如果子 View 的 dispatchTouchEvent 返回 true,
                //          则表示有子 View 处理了该事件,则设置 mFirstTouchTarget
                //如果子 View 的 dispatchTouchEvent 返回 false,
                //          则表示没有子 View 处理了该事件,则不设置 mFirstTouchTarget
                if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                    //该方法中设置了 mFirstTouchTarget = child
                    newTouchTarget = addTouchTarget(child, idBitsToAssign); 
                    alreadyDispatchedToNewTouchTarget = true;
                    break;
                }
            }
        }
    }

    // 4、判断是否有子 View 处理了事件
    if (mFirstTouchTarget == null) {
        handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
    } 
    else {
        if (alreadyDispatchedToNewTouchTarget){
             handled = true;
        }
        else{
            if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
                handled = true;
            }
        }
    }

    // 5、如果是一个事件序列的最后一个操作(ACTION_UP 或 ACTION_CANCEL),则把状态清空
    if (canceled 
            || actionMasked == MotionEvent.ACTION_UP
            || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
        resetTouchState();
    }
}

上面的代码中多处调用了 dispatchTransformedTouchEvent(),其中第三个参数有 null(第 52 行) 或者 child(第 40 行、第 59 行)。这个方法的实现如下:

public boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits){
    boolean handled = false;
    if (child == null) {
        handled = super.dispatchTouchEvent(event);
    } else {
        handled = child.dispatchTouchEvent(event);
    }
    return handled;
}

从上面代码可以看出,如果第三个参数传入 null,则调用 View 的 dispatchTouchEvent() 即自己处理事件;如果第三个参数传入 child,则将事件分发下去,即调用 child.dispatchTouchEvent()。

解释:

一些结论的验证

在网上有很多关于 Touch 事件的结论,这些结论其实都可以通过分析上面的代码得出。这里举几个例子:

参考文献

上一篇下一篇

猜你喜欢

热点阅读