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事件沒有被分發出去,即沒有被消費,那麼後面的事件也就不會被分發了。
這裏寫圖片描述

說明:

  • 都不攔截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事件,則之後的事件不會再向其分發。
這裏寫圖片描述

示例程序:

初始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事件,則不管其後續事件是否消費,只要不被攔截,則所有事件都會被分發到這裏。且被下層消費了的事件,不會再被上層消費。
這裏寫圖片描述

示例代碼:

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方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章