Android事件分发机制——Touch事件

Posted Jackwen

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android事件分发机制——Touch事件相关的知识,希望对你有一定的参考价值。

1. 简介

    先来看一个表:

Touch事件相关方法

功能

Activity

ViewGroup

View

public boolean dispatchTouchEvent(MotionEvent ev)

事件分发

Yes

Yes

Yes

public boolean onInterceptTouchEvent(MotionEvent ev)

事件拦截

No

Yes

No

public boolean onTouchEvent(MotionEvent ev)

事件响应

Yes

Yes

Yes

     从表中可以看出,Activity、ViewGroup、View都关心Touch事件,其中ViewGroup的关心的事件有三个:onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent。Activity和View关心的事件只有两个:dispatchTouchEvent、onTouchEvent。也就是说只有ViewGroup可以对事件进行拦截。

    android的UI结构是一个树形结构,树的最顶层(根节点)是Activity内包含的一个ViewGroup,下面有若干个ViewGroup节点,每个节点下面又有若干个ViewGroup节点或者View节点,以此类推。Touch事件的传递和处理与这种结构密切相关。一次完整的触摸事件由一个ACTION_DOWN、零个或一个或多个ACTION_MOVE、一个ACTION_UP组成。

在分析源码之前,先简单了解下这三个函数。

(1) 事件分发: public boolean dispatchTouchEvent(MotionEvent ev)

    当Touch事件发生时,Activity的dispatchTouchEvent()方法会以隧道方式(从根节点依次往下传递直到最内层子节点,或在中间某一节点中由于某一条件停止传递)将事件传递给最外层View的dispatchTouchEvent()方法,并由该View的dispatchTouchEvent()方法对事件进行分发。

dispatchTouchEvent 的事件分发逻辑如下:

  • return true :事件会分发给当前View并由dispatchTouchEvent()方法进行消费,同时事件会停止向下传递。
  • return false :将事件返还给当前View的上一级的onTouchEvent()进行消费。(这个上一级可能是Activity,也可能是父View)
  • return super.dispatchTouchEvent(ev) :事件会自动的分发给当前View的onInterceptTouchEvent方法。

    注意,View响应dispatchTouchEvent()和onInterceptTouchEvent()的前提是可以向该View中添加子View,也就是说该View有子节点才谈得上能分发和拦截。

(2) 事件拦截public boolean onInterceptTouchEvent(MotionEvent ev) 

    在外层View的dispatchTouchEvent()方法返回super.dispatchTouchEvent(ev)时,事件会自动的分发给当前View的onInterceptTouchEvent()方法。

onInterceptTouchEvent 的事件拦截逻辑如下:

  • return true :将对事件进行拦截,并将拦截到的事件交由当前View的onTouchEvent()进行处理。
  • return false :将对事件进行放行,当前View上的事件会被传递到子View 上,再由子View的dispatchTouchEvent()来继续对这个事件进行分发。
  • return super.onInterceptTouchEvent(ev) :事件默认会被拦截,并将拦截到的事件交由当前View的onTouchEvent()进行处理。

(3) 事件响应public boolean onTouchEvent(MotionEvent ev)

    由(1)和(2)可以知道onTouchEvent()被调用的条件。

onTouchEvent 的事件响应逻辑如下:

  • return false :事件将会从当前View向上传递,并且都是由上层View的onTouchEvent()来接收,如果传递到上层的onTouchEvent()也返回false,那么这                       个事件就会"消失",而且接收不到下一次事件。
  • return true :接收并消费掉该事件。
  • return super.onTouchEvent(ev) :默认处理事件的逻辑和return false相同。

下面看下源码。

1.1 Activity对Touch事件的处理

      当Touch事件发生生,最先被触发的是Activity的dispatchTouchEvent()函数,再由这个函数触发根节点的dispatchTouchEvent()。如果想让Activity不响应触摸事件,可以直接重写这个函数。

dispatchTouchEvent()@Activity.java

// 处理触摸屏事件。可以重写这个函数来拦截所有的触摸屏事件,不让它们分发到window。为了应该被正常处理的触摸屏幕事件,一定要确保调用这个实现。
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {  // 从ACTION_DOWN事件开始本次触摸事件
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {  // 会调用window中的根ViewGroup进行事件分发
        return true;   // 如果子View消费了这个事件,返回true.
    }
    return onTouchEvent(ev);    // 没有View处理本次事件,调用Activity的onTouchEvent。
}

 onTouchEvent()@Activity.java

// 当一个触摸屏事件没有被它下面的任意view处理的时候,调用这个函数。这个处理对于触摸事件超出window范围的时候很有用,这个时候没有view接收到事件。
public boolean onTouchEvent(MotionEvent event) {
    if (mWindow.shouldCloseOnTouch(this, event)) {
        finish();
        return true;
    }
    return false;
}

1.2 View对Touch事件的处理

dispatchTouchEvent()@View.java

// 将屏幕触摸事件向下传递给目标view.
public boolean dispatchTouchEvent(MotionEvent event) {
    ......
    if (onFilterTouchEventForSecurity(event)) {
        // ListenerInfo类保存了设给这个view的各种listener,如OnClickListener,OnTouchListener等。
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                       && li.mOnTouchListener.onTouch(this, event)) {
            // 当mOnTouchListener不为null,当前控件是enable的,且mOnTouchListener.onTouch返回true, 那么dispatchTouchEvent返回true.
            // 从这里可以看到onTouch执行要先于onTouchEvent及onClick
            return true;
        }
        // 上面的条件都不成立,才会执行onTouchEvent()
        if (onTouchEvent(event)) {       // 如果一个控件是可点击的,那么onTouchEvent必定返回true, 因此dispatchTouchEvent也返回true.
            return true;
        }
    }
    ......
    return false;
} 

    从View的dispatchTouchEvent()的实现知道,如果给一个view设置了OnTouchListener,那么onTouch函数就在dispatchTouchEvent()函数中被回调,只有onTouch()返回false时,才会继续执行onTouchEvent()。

onTouchEvent()@View.java

// 实现这个方法处理屏幕触摸手势事件
// 如果这个方法被用于检测点击动作,那么推荐通过调用performClick来实现。这样做可以确保系统行为一致,包括服从点击声音喜好,调度OnClickListener调用,当辅助 // 功能启用的时候处理ACTION_CLICK ACTION_CLICK。
public boolean onTouchEvent(MotionEvent event) {
    final int viewFlags = mViewFlags;
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);    // 设置按下UI状态
        }
        // 一个可点击的但是disabled的view仍然消费touch事件,只是不作出回应。
        return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
    }
    ......    
    if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                    // 如果我们没有拥有它,我们应该获取焦点,而且应该在Touch模式下。
                    boolean focusTaken = false;
                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                        focusTaken = requestFocus();
                    }
    
                    if (prepressed) {
                        // 按钮在我们实际显示它按下之前被释放。现在使它显示按下状态(在调度click之前),以确保用户看到它。
                        setPressed(true);
                    }

                    if (!mHasPerformedLongPress) {
                        // 这是一个阀门,所以移除长按的检查
                        removeLongPressCallback();
                        // 只有在按下状态中时才执行click动作。
                        if (!focusTaken) {
                            // 使用Runnable和post执行performClick,而不是直接执行。这样可以让这个view的其它可见状态在click操作开始前更新。
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            if (!post(mPerformClick)) {
                                performClick();              // 注意,在MotionEvent.ACTION_UP时执行click。
                            }
                        }
                    }
                    if (mUnsetPressedState == null) {
                        mUnsetPressedState = new UnsetPressedState();
                    }
                    if (prepressed) {
                        postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration());
                    } else if (!post(mUnsetPressedState)) {
                        mUnsetPressedState.run();
                    }
                    removeTapCallback();
                }
                break;
    
            case MotionEvent.ACTION_DOWN:
                mHasPerformedLongPress = false;
                if (performButtonActionOnTouchDown(event)) {
                    break;
                }
    
                // 沿层次结构走,以确定我们是否在一个滚动的容器内。
                boolean isInScrollingContainer = isInScrollingContainer();
                // 对于在一个滚动容器内的views,延迟一会按下的反馈,以防这是一个滚动。
                if (isInScrollingContainer) {
                    mPrivateFlags |= PFLAG_PREPRESSED;
                    if (mPendingCheckForTap == null) {
                        mPendingCheckForTap = new CheckForTap();
                    }
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                } else {
                    // 如果不是在一个正在滚动的容器内,那么马上显示反馈。
                    setPressed(true);
                    checkForLongClick(0);
                }
                break;
    
            case MotionEvent.ACTION_CANCEL:
                setPressed(false);
                removeTapCallback();
                removeLongPressCallback();
                break;

            case MotionEvent.ACTION_MOVE:
                final int x = (int) event.getX();
                final int y = (int) event.getY();
                // 按钮之外的移动要容忍。
                if (!pointInView(x, y, mTouchSlop)) {
                    // Outside button
                    removeTapCallback();
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                        // 移除后面长按/阀门的检查
                        removeLongPressCallback();
                        setPressed(false);
                    }
                }
            break;
        }
        return true; // 如果一个控件是可点击的,那么点击该控件时,onTouchEvent的返回值必定是true
    } 
    return false;
} 

performClick()@View.java

// 如果定义了OnClickListener,则调用。执行所有与click关联的动作:报告可访问性事件,播放按键声音。
public boolean performClick() {
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    
    ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        li.mOnClickListener.onClick(this);
        return true;
    }
    return false;
}

 setOnClickListener()@View.java

public void setOnClickListener(OnClickListener l) {
    if (!isClickable()) {
        setClickable(true);  // 如果这个view不是clickable,将它设置为clickable。
    }
    getListenerInfo().mOnClickListener = l;
}

     如果给一个View设置了OnClickListener,那么onClick()事件是在onTouchEvent()函数中被调用的。

1.3 ViewGroup对Touch事件的处理

onInterceptTouchEvent()@ViewGroup.java

// 实现这个方法来拦截所有触摸屏移动事件。这可以让你在事件被分配给孩子的时候看到事件,并在任意时刻获取当前手势的所有权。
// 使用这个功能需要特别注意,因为它与View.onTouchEvent有着相当复杂的相互作用,并且使用它还需要以正确的方式实现这个方法。
// 事件将会以下面顺序被接收:
// (1) ViewGroup会在这里接收到DOWN事件,这个DOWN事件会被这个ViewGroup的一个子View处理掉,或者给这个ViewGroup自己的onTouchEvent函数进行处理。那就意味着ViewGroup应该实现onTouchEvent()函数并且返回true, 这样你就会继续看到手势的后续事件(而不是寻找父View来处理)。
// (2) 通过从ViewGroup自己的onTouchEvent()返回true, ViewGroup的onInterceptTouchEvent()中将不会再收到任何后续事件,即后续事件不用再经过onInterceptTouchEvent()了,所有触摸的处理都像正常情况一样发生在onTouchEvent里了。
// (3) 只要ViewGroup从这个函数返回false,接下来的每个事件(包括最后的UP)都会首先传递到这里,然后再传递给目标View的onTouchEvent()。
// (4) 如果ViewGroup从这个函数返回true,说明从子view拦截了事件,并将它们通过onTouchEvent()传递给这个ViewGroup。当前目标View将接收到相同事件,但是Action为ACTION_CANCEL,并且没有进一步消息在这里传递。

public boolean onInterceptTouchEvent(MotionEvent ev) {  
    return false;  
}

dispatchTouchEvent()@ViewGroup.java

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
    }
    
    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        // 处理最初的down事件
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // 开始一个新的触摸手势时,丢掉所有之前的状态。framework可能由于app切换,ANR或者其它状态变化而放弃前一个手势的up或者cancel事件。
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }

        // 检查拦截
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;       // 是否禁用事件拦截功能,默认为false.
            if (!disallowIntercept) {
                intercepted = onInterceptTouchEvent(ev);// 根据onInterceptTouchEvent返回值决定是否拦截,返回false表示不拦截,返回true表示拦截。
                ev.setAction(action); // 重新保存action,以防它发生变化。
            } else {
                intercepted = false;
            }
        } else {
            // 没有触摸目标且action不是最初的DOWN事件,那么这个ViewGroup继续拦截触摸事件。
            intercepted = true;
        }

        // 检查取消
        final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL;

        // 如果需要,更新触摸目标列表给向下的指针。
        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        if (!canceled && !intercepted) { // 如果不是CANCEL,而且不拦截事件,则往下走。intercepted的值在前面被赋值。intercepted为true,则事件不再往下传。
            if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                            || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                final int actionIndex = ev.getActionIndex();  // DOWN事件这个值通常为0
                final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS;
    
                // 为该指针id除早期的触摸目标,以防它们已经不同步了。
                removePointersFromTouchTargets(idBitsToAssign);
                final int childrenCount = mChildrenCount;
                if (newTouchTarget == null && childrenCount != 0) {
                    final float x = ev.getX(actionIndex);
                    final float y = ev.getY(actionIndex);
                    // 从前往后扫描,找一个可以接收事件的子view。
                    final View[] children = mChildren;
                    final boolean customOrder = isChildrenDrawingOrderEnabled();
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
                        final View child = children[childIndex];
                        if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {
                            continue;
                        }
    
                        newTouchTarget = getTouchTarget(child);
                        if (newTouchTarget != null) {
                            // 子view已经在它的范围内接收触摸事件了。
                            // 给它新指针,除了它正在处理的指针。
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;      // 找到了接收事件的target,跳出循环。
                        }
    
                        resetCancelNextUpFlag(child);
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {   // dispatchTransformedTouchEvent函数中调用子view的dispatchTouchEvent。
                            // 子view在它的范围内接收触摸事件。
                            mLastTouchDownTime = ev.getDownTime();
                            mLastTouchDownIndex = childIndex;
                            mLastTouchDownX = ev.getX();
                            mLastTouchDownY = ev.getY();
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }
                    }
                }
    
                if (newTouchTarget == null && mFirstTouchTarget != null) {
                    // 没有找到一个子view来接收事件,将指针指向最近添加的目标。
                    newTouchTarget = mFirstTouchTarget;
                    while (newTouchTarget.next != null) {
                        newTouchTarget = newTouchTarget.next;
                    }
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                }
            }
        }
        // 分发给触摸目标
        if (mFirstTouchTarget == null) {
            // 没有触摸目标,那么把它当作一个普通的view。
            handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
        } else {
            // 分发给触摸目标,如果我们已经分发给它那么就排除新的触摸目标。如有必要取消触摸目标。
            TouchTarget predecessor = null;
            TouchTarget target = mFirstTouchTarget;
            while (target != null) {
                final TouchTarget next = target.next;
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
                    if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                    if (cancelChild) {
                        if (predecessor == null) {
                            mFirstTouchTarget = next;
                        } else {
                            predecessor.next = next;
                        }
                        target.recycle();
                        target = next;
                        continue;
                    }
                }
                predecessor = target;
                target = next;
            }
        }
    
        // Update list of touch targets for pointer up or cancel, if needed.
        // 如果需要,为指针UP或者CANCEL更新触摸目标列表。
        if (canceled || actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            resetTouchState();
        } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
            final int actionIndex = ev.getActionIndex();
            final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
            removePointersFromTouchTargets(idBitsToRemove);
        }
    }
    
    if (!handled && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
    }
    return handled;
}

 dispatchTransformedTouchEvent()@ViewGroup.java

// 将手势事件转换成特定子view的坐标空间,过滤无关的指针ID,如有必要覆盖它的action。如果child为null,则取而代之的是假定手势事件会发送给该ViewGroup。
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) {
    final boolean handled;
 
    // 取消手势动作是一种特殊情况。不需要执行任何转换或者过滤操作。重点关注的是action,而不是内容。
    final int oldAction = event.getAction();
    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
        event.setAction(MotionEvent.ACTION_CANCEL);
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else {
            handled = child.dispatchTouchEvent(event);     // 调用child的dispatchTouchEvent。
        }
        event.setAction(oldAction);
        return handled;
    }

    // 结算要传递的指针数量。
    final int oldPointerIdBits = event.getPointerIdBits();
    final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

    // 如果因为某种原因,我们在一个不符的状态中结束了,在这个状态中看起来我们可能会产生一个不带指针的手势事件,那么就放弃这个事件。
    if (newPointerIdBits == 0) {
        return false;
    }

    // 如果指针的数量是相等的,那么我们就不需要执行任何代价高的不可逆转的转换,那么只要我们小心恢复我们做的任何修改,我们就可以在这次分发中重用手势事件。
    // 否则,我们就需要复制一个手势事件。
    final MotionEvent transformedEvent;
    if (newPointerIdBits == oldPointerIdBits) {
        if (child == null || child.hasIdentityMatrix()) {
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                final float offsetX = mScrollX - child.mLeft;
                final float offsetY = mScrollY - child.mTop;
                event.offsetLocation(offsetX, offsetY);
    
                handled = child.dispatchTouchEvent(event);

                event.offsetLocation(-offsetX, -offsetY);
            }
            return handled;
        }
        transformedEvent = MotionEvent.obtain(event);
    } else {
        transformedEvent = event.split(newPointerIdBits);
    }

    // 执行任何需要的转换和分发。
    if (child == null) {
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        final float offsetX = mScrollX - child.mLeft;
        final float offsetY = mScrollY - child.mTop;
        transformedEvent.offsetLocation(offsetX, offsetY);
        if (! child.hasIdentityMatrix()) {
            transformedEvent.transform(child.getInverseMatrix());
        }
        handled = child.dispatchTouchEvent(transformedEvent); //若一个控件是可点击的,则点击该控件时,dispatchTouchEvent的返回值必定是true
    }
    // 完成
    transformedEvent.recycle();
    return handled;
}

 



以上是关于Android事件分发机制——Touch事件的主要内容,如果未能解决你的问题,请参考以下文章

Android Touch事件分发(源码分析)

Android Touch事件分发(源码分析)

Android 进阶——Framework 核心之Touch事件分发机制详细攻略

Android 进阶——Framework 核心之Touch事件分发机制详细攻略

Android 进阶——Framework 核心之Touch事件分发机制详细攻略

Android 编程下 Touch 事件的分发和消费机制