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

来源:互联网 发布:云计算应用 编辑:程序博客网 时间:2024/05/14 05:38


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;}
复制代码
转自:http://www.cnblogs.com/Jackwen/p/5239035.html
原创粉丝点击