Android触摸事件分发机制详解

  • dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()方法源码解析
  • 各种触摸事件分发、消费情况详解

dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()方法源码解析

  Android触摸事件分发过程中最重要的就是dispatchTouchEvent()onInterceptTouchEvent()onTouchEvent()方法。其中onInterceptTouchEvent()方法只存在于ViewGroup中,View中没有,其用于事件的拦截。dispatchTouchEvent()方法用于事件的分发,onTouchEvent()方法用于事件的消费。
  下面给出这三个主要方法的部分主要源码实现,并做出分析。
  ViewGroup的dispatchTouchEvent方法中有下面这段代码:

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); // restore action in case it was changed
            } else {
                intercepted = false;
            }
        } else {
            // There are no touch targets and this action is not an initial down
            // so this view group continues to intercept touches.
            intercepted = true;
        }

  这段代码用来判断是否拦截该事件,从代码中可知,当触发down事件或已存在之前消费过事件的对象时,需要通过disallowInterceptonInterceptTouchEvent(ev)两个条件来判断是否要拦截该事件。其中disallowIntercept默认得到的值为false,即允许拦截,其值可通过requestDisallowInterceptTouchEvent方法设置,当disallowIntercept为false时,接下来调用onInterceptTouchEvent(ev)方法进行判断。所以可通过重写onInterceptTouchEvent(ev)方法对各种事件进行具体的事件拦截。
  dispatchTouchEvent中还会间接调用到如下代码段:

// Perform any necessary transformations and dispatch.
        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);
        }

  这段代码展示了,ViewGroup的dispatchTouchEvent会调用其子视图的dispatchTouchEvent方法,将事件向下分发。
  事件以上面的形式,一层层向内传递,若未经拦截,到达View的dispatchTouchEvent方法时,其中有下面一段代码:

//noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }

  这段代码中,有一个很长的判定条件,其结果决定了是否执行onTouchEvent方法。首先我们看mOnTouchListener在哪进行设置,找到如下代码:

public void setOnTouchListener(OnTouchListener l) {
        getListenerInfo().mOnTouchListener = l;
    }
ListenerInfo getListenerInfo() {
        if (mListenerInfo != null) {
            return mListenerInfo;
        }
        mListenerInfo = new ListenerInfo();
        return mListenerInfo;
    }

  所以当对视图调用setOnTouchListener方法设置监听后,li != null && li.mOnTouchListener != null两个条件就得到了满足,而(mViewFlags & ENABLED_MASK) == ENABLED的判断是该视图是否处于enable状态,其默认为true,所以当OnTouchListener中的onTouch方法返回true时,就拦截了事件向onTouchEvent方法传递。下面看一下View的onTouchEvent方法中都执行了一些什么操作:

public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return (((viewFlags & CLICKABLE) == CLICKABLE
                    || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
        }

        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        // take focus if we don't have it already and we should in
                        // touch mode.
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                        if (prepressed) {
                            // The button is being released before we actually
                            // showed it as pressed. Make it show the pressed
                            // state now (before scheduling the click) to ensure
                            // the user sees it.
                            setPressed(true, x, y);
                       }

                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();

                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }

                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }

                        if (prepressed) {
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }

                        removeTapCallback();
                    }
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = false;

                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

                    // Walk up the hierarchy to determine if we're inside a scrolling container.
                    boolean isInScrollingContainer = isInScrollingContainer();

                    // For views inside a scrolling container, delay the pressed feedback for
                    // a short period in case this is a scroll.
                    if (isInScrollingContainer) {
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true, x, y);
                        checkForLongClick(0);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    setPressed(false);
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_MOVE:
                    drawableHotspotChanged(x, y);

                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        removeTapCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            // Remove any future long press/tap checks
                            removeLongPressCallback();

                            setPressed(false);
                        }
                    }
                    break;
            }

            return true;
        }

        return false;
    }

  方法很长,其中定义了对down,move,up等事件的处理。我们知道如果在视图上设置了OnTouchListener并在其onTouch方法中返回true,该视图的onClick方法将不再被触发,我们还知道,onClick方法在up事件时被触发。我们查看上述代码对up事件的处理时,发现有一个performClick方法,该方法的代码如下:

public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        return result;
    }

  是不是看到了li.mOnClickListener.onClick(this)这行代码,这就是onClick被触发回调的方法,通过上面的分析知道,如果设置的OnTouchListener的onTouch方法中返回true,则将不会执行到这段回调代码,所以导致onClick方法失效。
  所以总体上,当一个触摸事件开始时,最外层ViewGroup捕获该事件,并开始调用其dispatchTouchEvent方法进行事件分发,该方法会根据本次触摸的之前事件的拦截和消费情况,来决定怎样调用其内层的ViewGroup的dispatchTouchEventonInterceptTouchEventonTouchEvent方法或View的dispatchTouchEventonTouchEvent方法,来将事件更深层的分发和消费,这些方法的处理结果,又逐级回传,从而进一步影响后面事件的分发和消费方式。
  接下来,以上面的源码分析为基础,具体分析触摸事件分发处理情况。

各种触摸事件分发、消费情况详解

ACTION_DOWN事件未被消费,分发失败

  在一个触摸事件中,down事件起着至关重要的作用,down事件就是“敲门”事件,如果门没有敲开,后面的事件也就没有意义了,所以如果down事件没有被分发出去,即没有被消费,那么后面的事件也就不会被分发了。
Android触摸事件分发机制详解_第1张图片

说明:

  • 都不拦截ACTION_DOWN事件,会依次向下传递
  • 都不消费ACTION_DOWN事件,onTouchEvent会依次向上传递
  • 都不消费ACTION_DOWN事件,dispatchTouchEvent会返回false,表示事件没有被派发出去
  • ACTION_DOWN事件没有被消费,后续的ACTION_MOVE、ACTION_UP等事件都不会再被传递

示例程序:

Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        String motionEvent = null;
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                motionEvent = "down";
                return false;
            case MotionEvent.ACTION_MOVE:
                motionEvent = "move";
                break;
            case MotionEvent.ACTION_UP:
                motionEvent = "up";
                break;
            case MotionEvent.ACTION_CANCEL:
                motionEvent = "cancel";
                break;
        }
        Log.i(SXD, TAG + "--dispatchTouchEvent++in++motionEvent:" + motionEvent);
        boolean ret = super.dispatchTouchEvent(ev);
        Log.i(SXD, TAG + "--dispatchTouchEvent++out++ret:" + ret + ",motionEvent:" + motionEvent);
        return ret;
    }

  该程序没有log输出,则证明down事件没有分发成功后,在没有后续事件进行分发。

ACTION_DOWN事件被消费,后续事件被拦截

  如果视图消费了down事件,且之后有事件被拦截,则每种被拦截事件类型(move,up等)都会触发收到一次cancel事件。

示例程序:

onInterceptTouchEvent在Move事件返回true
Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        String motionEvent = null;
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                motionEvent = "down";
                break;
            case MotionEvent.ACTION_MOVE:
                motionEvent = "move";
                return true;
            case MotionEvent.ACTION_UP:
                motionEvent = "up";
                break;
            case MotionEvent.ACTION_CANCEL:
                motionEvent = "cancel";
                break;
        }
        Log.i(SXD, TAG + "--onInterceptTouchEvent++in++motionEvent:" + motionEvent);
        boolean ret = super.onInterceptTouchEvent(ev);
        Log.i(SXD, TAG + "--onInterceptTouchEvent++out++ret:" + ret + ",motionEvent:" + motionEvent);
        return ret;
    }
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++in++motionEvent:down
I/sxd: TouchTestRelativeLayout--onInterceptTouchEvent++in++motionEvent:down
I/sxd: TouchTestRelativeLayout--onInterceptTouchEvent++out++ret:false,motionEvent:down
I/sxd: TouchButton--dispatchTouchEvent++in++motionEvent:down
I/sxd: TouchButton--onTouch++ret:false,motionEvent:down
I/sxd: TouchButton--onTouchEvent++in++motionEvent:down
I/sxd: TouchButton--onTouchEvent++out++ret:true,motionEvent:down
I/sxd: TouchButton--dispatchTouchEvent++out++ret:true,motionEvent:down
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++out++ret:true,motionEvent:down
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++in++motionEvent:move
I/sxd: TouchButton--dispatchTouchEvent++in++motionEvent:cancel
I/sxd: TouchButton--onTouch++ret:false,motionEvent:cancel
I/sxd: TouchButton--onTouchEvent++in++motionEvent:cancel
I/sxd: TouchButton--onTouchEvent++out++ret:true,motionEvent:cancel
I/sxd: TouchButton--dispatchTouchEvent++out++ret:true,motionEvent:cancel
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++out++ret:true,motionEvent:move
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++in++motionEvent:move
I/sxd: TouchTestRelativeLayout--onTouch++ret:false,motionEvent:move
I/sxd: TouchTestRelativeLayout--onTouchEvent++in++motionEvent:move
I/sxd: TouchTestRelativeLayout--onTouchEvent++out++ret:true,motionEvent:move
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++out++ret:true,motionEvent:move
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++in++motionEvent:move
I/sxd: TouchTestRelativeLayout--onTouch++ret:false,motionEvent:move
I/sxd: TouchTestRelativeLayout--onTouchEvent++in++motionEvent:move
I/sxd: TouchTestRelativeLayout--onTouchEvent++out++ret:true,motionEvent:move
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++out++ret:true,motionEvent:move
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++in++motionEvent:up
I/sxd: TouchTestRelativeLayout--onTouch++ret:false,motionEvent:up
I/sxd: TouchTestRelativeLayout--onTouchEvent++in++motionEvent:up
I/sxd: TouchTestRelativeLayout--onTouchEvent++out++ret:true,motionEvent:up
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++out++ret:true,motionEvent:up

  结果表明,当onInterceptTouchEvent在move事件时返回true进行事件拦截时,之前的down事件可以向下分发,但第一次的move事件将触发之前处理down事件的子View触发cancel事件,且本次move事件不会被拦截层消费,但之后的move,up等事件可被拦截层消费。

onInterceptTouchEvent在Up事件返回true
@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        String motionEvent = null;
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                motionEvent = "down";
                break;
            case MotionEvent.ACTION_MOVE:
                motionEvent = "move";
                break;
            case MotionEvent.ACTION_UP:
                motionEvent = "up";
                return true;
            case MotionEvent.ACTION_CANCEL:
                motionEvent = "cancel";
                break;
        }
        Log.i(SXD, TAG + "--onInterceptTouchEvent++in++motionEvent:" + motionEvent);
        boolean ret = super.onInterceptTouchEvent(ev);
        Log.i(SXD, TAG + "--onInterceptTouchEvent++out++ret:" + ret + ",motionEvent:" + motionEvent);
        return ret;
    }
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++in++motionEvent:down
I/sxd: TouchTestRelativeLayout--onInterceptTouchEvent++in++motionEvent:down
I/sxd: TouchTestRelativeLayout--onInterceptTouchEvent++out++ret:false,motionEvent:down
I/sxd: TouchButton--dispatchTouchEvent++in++motionEvent:down
I/sxd: TouchButton--onTouch++ret:false,motionEvent:down
I/sxd: TouchButton--onTouchEvent++in++motionEvent:down
I/sxd: TouchButton--onTouchEvent++out++ret:true,motionEvent:down
I/sxd: TouchButton--dispatchTouchEvent++out++ret:true,motionEvent:down
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++out++ret:true,motionEvent:down
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++in++motionEvent:move
I/sxd: TouchTestRelativeLayout--onInterceptTouchEvent++in++motionEvent:move
I/sxd: TouchTestRelativeLayout--onInterceptTouchEvent++out++ret:false,motionEvent:move
I/sxd: TouchButton--dispatchTouchEvent++in++motionEvent:move
I/sxd: TouchButton--onTouch++ret:false,motionEvent:move
I/sxd: TouchButton--onTouchEvent++in++motionEvent:move
I/sxd: TouchButton--onTouchEvent++out++ret:true,motionEvent:move
I/sxd: TouchButton--dispatchTouchEvent++out++ret:true,motionEvent:move
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++out++ret:true,motionEvent:move
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++in++motionEvent:move
I/sxd: TouchTestRelativeLayout--onInterceptTouchEvent++in++motionEvent:move
I/sxd: TouchTestRelativeLayout--onInterceptTouchEvent++out++ret:false,motionEvent:move
I/sxd: TouchButton--dispatchTouchEvent++in++motionEvent:move
I/sxd: TouchButton--onTouch++ret:false,motionEvent:move
I/sxd: TouchButton--onTouchEvent++in++motionEvent:move
I/sxd: TouchButton--onTouchEvent++out++ret:true,motionEvent:move
I/sxd: TouchButton--dispatchTouchEvent++out++ret:true,motionEvent:move
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++out++ret:true,motionEvent:move
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++in++motionEvent:move
I/sxd: TouchTestRelativeLayout--onInterceptTouchEvent++in++motionEvent:move
I/sxd: TouchTestRelativeLayout--onInterceptTouchEvent++out++ret:false,motionEvent:move
I/sxd: TouchButton--dispatchTouchEvent++in++motionEvent:move
I/sxd: TouchButton--onTouch++ret:false,motionEvent:move
I/sxd: TouchButton--onTouchEvent++in++motionEvent:move
I/sxd: TouchButton--onTouchEvent++out++ret:true,motionEvent:move
I/sxd: TouchButton--dispatchTouchEvent++out++ret:true,motionEvent:move
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++out++ret:true,motionEvent:move
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++in++motionEvent:up
I/sxd: TouchButton--dispatchTouchEvent++in++motionEvent:cancel
I/sxd: TouchButton--onTouch++ret:false,motionEvent:cancel
I/sxd: TouchButton--onTouchEvent++in++motionEvent:cancel
I/sxd: TouchButton--onTouchEvent++out++ret:true,motionEvent:cancel
I/sxd: TouchButton--dispatchTouchEvent++out++ret:true,motionEvent:cancel
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++out++ret:true,motionEvent:up

  结果表明,当onInterceptTouchEvent在up事件时返回true进行事件拦截时,之前的触摸事件可以向下分发,但本次up事件将触发之前处理事件的子View触发cancel事件,且本次cancel事件不会被拦截层消费。

ACTION_DOWN事件被拦截

  如果视图没有消费down事件,则之后的事件不会再向其分发。
Android触摸事件分发机制详解_第2张图片

示例程序:

初始down事件被上层拦截
@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        String motionEvent = null;
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                motionEvent = "down";
                return true;
            case MotionEvent.ACTION_MOVE:
                motionEvent = "move";
                break;
            case MotionEvent.ACTION_UP:
                motionEvent = "up";
                break;
            case MotionEvent.ACTION_CANCEL:
                motionEvent = "cancel";
                break;
        }
        Log.i(SXD, TAG + "--onInterceptTouchEvent++in++motionEvent:" + motionEvent);
        boolean ret = super.onInterceptTouchEvent(ev);
        Log.i(SXD, TAG + "--onInterceptTouchEvent++out++ret:" + ret + ",motionEvent:" + motionEvent);
        return ret;
    }
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++in++motionEvent:down
I/sxd: TouchTestRelativeLayout--onTouch++ret:false,motionEvent:down
I/sxd: TouchTestRelativeLayout--onTouchEvent++in++motionEvent:down
I/sxd: TouchTestRelativeLayout--onTouchEvent++out++ret:true,motionEvent:down
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++out++ret:true,motionEvent:down
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++in++motionEvent:move
I/sxd: TouchTestRelativeLayout--onTouch++ret:false,motionEvent:move
I/sxd: TouchTestRelativeLayout--onTouchEvent++in++motionEvent:move
I/sxd: TouchTestRelativeLayout--onTouchEvent++out++ret:true,motionEvent:move
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++out++ret:true,motionEvent:move
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++in++motionEvent:move
I/sxd: TouchTestRelativeLayout--onTouch++ret:false,motionEvent:move
I/sxd: TouchTestRelativeLayout--onTouchEvent++in++motionEvent:move
I/sxd: TouchTestRelativeLayout--onTouchEvent++out++ret:true,motionEvent:move
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++out++ret:true,motionEvent:move
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++in++motionEvent:up
I/sxd: TouchTestRelativeLayout--onTouch++ret:false,motionEvent:up
I/sxd: TouchTestRelativeLayout--onTouchEvent++in++motionEvent:up
I/sxd: TouchTestRelativeLayout--onTouchEvent++out++ret:true,motionEvent:up
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++out++ret:true,motionEvent:up
I/sxd: TouchTestRelativeLayout--onClick
View未消费down事件
@Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        String motionEvent = null;
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                motionEvent = "down";
                return false;
            case MotionEvent.ACTION_MOVE:
                motionEvent = "move";
                break;
            case MotionEvent.ACTION_UP:
                motionEvent = "up";
                break;
            case MotionEvent.ACTION_CANCEL:
                motionEvent = "cancel";
                break;
        }
        Log.i(SXD, TAG + "--onTouchEvent++in++motionEvent:" + motionEvent);
        boolean ret = super.onTouchEvent(event);
        Log.i(SXD, TAG + "--onTouchEvent++out++ret:" + ret + ",motionEvent:" + motionEvent);
        return ret;
    }
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++in++motionEvent:down
I/sxd: TouchTestRelativeLayout--onInterceptTouchEvent++in++motionEvent:down
I/sxd: TouchTestRelativeLayout--onInterceptTouchEvent++out++ret:false,motionEvent:down
I/sxd: TouchButton--dispatchTouchEvent++in++motionEvent:down
I/sxd: TouchButton--onTouch++ret:false,motionEvent:down
I/sxd: TouchButton--dispatchTouchEvent++out++ret:false,motionEvent:down
I/sxd: TouchTestRelativeLayout--onTouch++ret:false,motionEvent:down
I/sxd: TouchTestRelativeLayout--onTouchEvent++in++motionEvent:down
I/sxd: TouchTestRelativeLayout--onTouchEvent++out++ret:true,motionEvent:down
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++out++ret:true,motionEvent:down
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++in++motionEvent:move
I/sxd: TouchTestRelativeLayout--onTouch++ret:false,motionEvent:move
I/sxd: TouchTestRelativeLayout--onTouchEvent++in++motionEvent:move
I/sxd: TouchTestRelativeLayout--onTouchEvent++out++ret:true,motionEvent:move
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++out++ret:true,motionEvent:move
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++in++motionEvent:up
I/sxd: TouchTestRelativeLayout--onTouch++ret:false,motionEvent:up
I/sxd: TouchTestRelativeLayout--onTouchEvent++in++motionEvent:up
I/sxd: TouchTestRelativeLayout--onTouchEvent++out++ret:true,motionEvent:up
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++out++ret:true,motionEvent:up
I/sxd: TouchTestRelativeLayout--onClick

  结果表明,当视图没有消费down事件,则其后的所有事件都不会再分发给它,但会触发一次该视图的onTouch方法。

事件被消费后,不再上传

  只要视图消费了down事件,则不管其后续事件是否消费,只要不被拦截,则所有事件都会被分发到这里。且被下层消费了的事件,不会再被上层消费。
Android触摸事件分发机制详解_第3张图片

示例代码:

onTouchEvent未消费Move事件
@Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        String motionEvent = null;
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                motionEvent = "down";
                break;
            case MotionEvent.ACTION_MOVE:
                motionEvent = "move";
                return false;
            case MotionEvent.ACTION_UP:
                motionEvent = "up";
                break;
            case MotionEvent.ACTION_CANCEL:
                motionEvent = "cancel";
                break;
        }
        Log.i(SXD, TAG + "--onTouchEvent++in++motionEvent:" + motionEvent);
        boolean ret = super.onTouchEvent(event);
        Log.i(SXD, TAG + "--onTouchEvent++out++ret:" + ret + ",motionEvent:" + motionEvent);
        return ret;
    }
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++in++motionEvent:down
I/sxd: TouchTestRelativeLayout--onInterceptTouchEvent++in++motionEvent:down
I/sxd: TouchTestRelativeLayout--onInterceptTouchEvent++out++ret:false,motionEvent:down
I/sxd: TouchButton--dispatchTouchEvent++in++motionEvent:down
I/sxd: TouchButton--onTouch++ret:false,motionEvent:down
I/sxd: TouchButton--onTouchEvent++in++motionEvent:down
I/sxd: TouchButton--onTouchEvent++out++ret:true,motionEvent:down
I/sxd: TouchButton--dispatchTouchEvent++out++ret:true,motionEvent:down
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++out++ret:true,motionEvent:down
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++in++motionEvent:move
I/sxd: TouchTestRelativeLayout--onInterceptTouchEvent++in++motionEvent:move
I/sxd: TouchTestRelativeLayout--onInterceptTouchEvent++out++ret:false,motionEvent:move
I/sxd: TouchButton--dispatchTouchEvent++in++motionEvent:move
I/sxd: TouchButton--onTouch++ret:false,motionEvent:move
I/sxd: TouchButton--dispatchTouchEvent++out++ret:false,motionEvent:move
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++out++ret:false,motionEvent:move
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++in++motionEvent:move
I/sxd: TouchTestRelativeLayout--onInterceptTouchEvent++in++motionEvent:move
I/sxd: TouchTestRelativeLayout--onInterceptTouchEvent++out++ret:false,motionEvent:move
I/sxd: TouchButton--dispatchTouchEvent++in++motionEvent:move
I/sxd: TouchButton--onTouch++ret:false,motionEvent:move
I/sxd: TouchButton--dispatchTouchEvent++out++ret:false,motionEvent:move
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++out++ret:false,motionEvent:move
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++in++motionEvent:up
I/sxd: TouchTestRelativeLayout--onInterceptTouchEvent++in++motionEvent:up
I/sxd: TouchTestRelativeLayout--onInterceptTouchEvent++out++ret:false,motionEvent:up
I/sxd: TouchButton--dispatchTouchEvent++in++motionEvent:up
I/sxd: TouchButton--onTouch++ret:false,motionEvent:up
I/sxd: TouchButton--onTouchEvent++in++motionEvent:up
I/sxd: TouchButton--onTouchEvent++out++ret:true,motionEvent:up
I/sxd: TouchButton--dispatchTouchEvent++out++ret:true,motionEvent:up
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++out++ret:true,motionEvent:up
I/sxd: TouchButton--onClick

  结果表明,只要视图消费掉了down事件,且上层分发不再拦截,则其后的所有事件都会再分发给它,且其消费不对后续事件消费时,仅会触发其当前事件的onTouch方法。

onTouchEvent未消费Up事件
@Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        String motionEvent = null;
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                motionEvent = "down";
                break;
            case MotionEvent.ACTION_MOVE:
                motionEvent = "move";
                break;
            case MotionEvent.ACTION_UP:
                motionEvent = "up";
                return false;
            case MotionEvent.ACTION_CANCEL:
                motionEvent = "cancel";
                break;
        }
        Log.i(SXD, TAG + "--onTouchEvent++in++motionEvent:" + motionEvent);
        boolean ret = super.onTouchEvent(event);
        Log.i(SXD, TAG + "--onTouchEvent++out++ret:" + ret + ",motionEvent:" + motionEvent);
        return ret;
    }
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++in++motionEvent:down
I/sxd: TouchTestRelativeLayout--onInterceptTouchEvent++in++motionEvent:down
I/sxd: TouchTestRelativeLayout--onInterceptTouchEvent++out++ret:false,motionEvent:down
I/sxd: TouchButton--dispatchTouchEvent++in++motionEvent:down
I/sxd: TouchButton--onTouch++ret:false,motionEvent:down
I/sxd: TouchButton--onTouchEvent++in++motionEvent:down
I/sxd: TouchButton--onTouchEvent++out++ret:true,motionEvent:down
I/sxd: TouchButton--dispatchTouchEvent++out++ret:true,motionEvent:down
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++out++ret:true,motionEvent:down
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++in++motionEvent:move
I/sxd: TouchTestRelativeLayout--onInterceptTouchEvent++in++motionEvent:move
I/sxd: TouchTestRelativeLayout--onInterceptTouchEvent++out++ret:false,motionEvent:move
I/sxd: TouchButton--dispatchTouchEvent++in++motionEvent:move
I/sxd: TouchButton--onTouch++ret:false,motionEvent:move
I/sxd: TouchButton--onTouchEvent++in++motionEvent:move
I/sxd: TouchButton--onTouchEvent++out++ret:true,motionEvent:move
I/sxd: TouchButton--dispatchTouchEvent++out++ret:true,motionEvent:move
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++out++ret:true,motionEvent:move
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++in++motionEvent:up
I/sxd: TouchTestRelativeLayout--onInterceptTouchEvent++in++motionEvent:up
I/sxd: TouchTestRelativeLayout--onInterceptTouchEvent++out++ret:false,motionEvent:up
I/sxd: TouchButton--dispatchTouchEvent++in++motionEvent:up
I/sxd: TouchButton--onTouch++ret:false,motionEvent:up
I/sxd: TouchButton--dispatchTouchEvent++out++ret:false,motionEvent:up
I/sxd: TouchTestRelativeLayout--dispatchTouchEvent++out++ret:false,motionEvent:up

  结果表明,只要视图消费掉了down事件,且上层分发不再拦截,则其后的所有事件都会再分发给它,且其消费不对后续事件消费时,仅会触发其当前事件的onTouch方法。

你可能感兴趣的:(android,事件分发,触摸事件)