OnTouchListener、onTouchEvent、onClick的执行顺序

View---dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
        .......
        boolean result = false;

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            stopNestedScroll();
        }

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

            if (!result && onTouchEvent(event)) {//2
                result = true;
            }
        }
        ......
        return result;
    }

从2可知,当result为true时,则不会走onTouchEvent(event),只有result为false时,!result才为true,继续走onTouchEvent(event)。那继续分析1处的四个条件。

先来看mListenerInfo什么时候赋值?

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

从setOnTouchListener可知,当我们设置OnTouchListener事件的时候,mListenerInfo就会初始化,并且把mOnTouchListener进行赋值。

所以只要我们设置setOnTouchListener,li != null && li.mOnTouchListener != null就成立。第三个条件为使能条件,既然能设置监听事件,肯定为true.

li.mOnTouchListener.onTouch(this, event)则是onTouch的回调,如果返回true,则result = true,当result = true,则2处的onTouchEvent()则不会走。

反之,则onTouchEvent会走。所以OnTouchListener优先于onTouchEvent()执行。

看了一圈,都没有看到关于onClick(),所以猜测onClick()在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();

        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
            return clickable;
        }
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    if ((viewFlags & TOOLTIP) == TOOLTIP) {
                        handleTooltipUp();
                    }
                    if (!clickable) {
                        removeTapCallback();
                        removeLongPressCallback();
                        mInContextButtonPress = false;
                        mHasPerformedLongPress = false;
                        mIgnoreNextUpEvent = false;
                        break;
                    }
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                        if (prepressed) {
                            setPressed(true, x, y);
                        }

                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            if (!focusTaken) {
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClickInternal();
                                }
                            }
                        }

                        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 (!clickable) {
                        checkForLongClick(
                                ViewConfiguration.getLongPressTimeout(),
                                x,
                                y,
                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                        break;
                    }

                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

                    boolean isInScrollingContainer = isInScrollingContainer();

                
                    if (isInScrollingContainer) {
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        setPressed(true, x, y);
                        checkForLongClick(
                                ViewConfiguration.getLongPressTimeout(),
                                x,
                                y,
                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                    }
                    break;
                    
                case MotionEvent.ACTION_MOVE:
                // 判断当前的坐标是否在点击的按钮内部
                // 如果不在按钮内部则移除长按事件,如果pressed==true则将pressed设置为false,意味着不响应点击事件
                if (!pointInView(x, y, mTouchSlop)) {
                    removeLongPressCallback();
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                        setPressed(false);
                    }
                }
                break;
            ......
            return true;
        }

        return false;
    }

我们可以看到在ACTION_UP中会调用performClickInternal(),该方法就是调用onClick事件。

private boolean performClickInternal() {
        notifyAutofillManagerOnClick();
        return performClick();
    }
    
public boolean performClick() {
        notifyAutofillManagerOnClick();

        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;
        }
    return result;
    }

当我们设置了onClickListener的时候,就会调用onClick事件。反之不会。

观察到onClick返回值为void,这样做的目的表示该事件被消费了。

给一个button同时设置onClickListener和onLongClickListener,长按这个button,长按跟单击事件是否都能够得到响应?

首先我们是button,clickable默认是true,手指点下的时候,会走ACTION_DOWN事件。

case MotionEvent.ACTION_DOWN:
                    if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                        mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                    }
                    mHasPerformedLongPress = false;

                    if (!clickable) {
                        checkForLongClick(
                                ViewConfiguration.getLongPressTimeout(),
                                x,
                                y,
                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                        break;
                    }

                    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(
                                ViewConfiguration.getLongPressTimeout(),
                                x,
                                y,
                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                    }
                    break;

因为clickable为 true,所以第一个if不会进去;isInScrollingContainer表示是否在一个滚动的容器中。

布局中就一个button,显然不在滚动的容器中,所以会走else代码。

在down中设置pressed状态为true,然后发送一个延迟500毫秒的事件去执行长按事件。

private void checkForLongClick(long delay, float x, float y, int classification) {
        if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
            mHasPerformedLongPress = false;

            if (mPendingCheckForLongPress == null) {
                mPendingCheckForLongPress = new CheckForLongPress();
            }
            mPendingCheckForLongPress.setAnchor(x, y);
            mPendingCheckForLongPress.rememberWindowAttachCount();
            mPendingCheckForLongPress.rememberPressedState();
            mPendingCheckForLongPress.setClassification(classification);
            postDelayed(mPendingCheckForLongPress, delay);
        }
    }
    
private final class CheckForLongPress implements Runnable {
        private int mOriginalWindowAttachCount;
        private float mX;
        private float mY;
        private boolean mOriginalPressedState;
        /**
         * The classification of the long click being checked: one of the
         * StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__* constants.
         */
        private int mClassification;

        @Override
        public void run() {
            if ((mOriginalPressedState == isPressed()) && (mParent != null)
                    && mOriginalWindowAttachCount == mWindowAttachCount) {
                recordGestureClassification(mClassification);
                if (performLongClick(mX, mY)) {
                    mHasPerformedLongPress = true;
                }
            }
        }
    }

执行了onLongClick事件之后,将mHasPerformedLongPress设置为true。

然后回到ACTION_UP

                case MotionEvent.ACTION_UP:
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    if ((viewFlags & TOOLTIP) == TOOLTIP) {
                        handleTooltipUp();
                    }
                    if (!clickable) {
                        removeTapCallback();
                        removeLongPressCallback();
                        mInContextButtonPress = false;
                        mHasPerformedLongPress = false;
                        mIgnoreNextUpEvent = false;
                        break;
                    }
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                        if (prepressed) {
                            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) {
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClickInternal();
                                }
                            }
                        }

                        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;

因为在ACTION_DOWN中把mHasPerformedLongPress设置为true,所以就不会去执行performClickInternal(),也就是不会去执行onClick()。

button同时设置onClickListener和onLongClickListener,长按这个button,最终只能长按事件得到响应。

界面上有两个button,点击一个按钮,然后滑到第二个按钮抬起,如果这两个按钮都设置了点击事件,会不会触发?

由于滑动,所以会走ACTION_MOVE事件。

                case MotionEvent.ACTION_MOVE:
                
                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, touchSlop)) {
                        removeTapCallback();
                        removeLongPressCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            setPressed(false);
                        }
                        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    }
                    ......

                    break;

当手指滑动的时候,在move事件中时刻监听当然手指所在的坐标点,如果当前坐标不在按钮内部,那么就移除长按事件,并且设置pressed=false。

点击第一个按钮,然后滑动到第二个按钮后抬起,第一个按钮会执行一个完整的事件序列(down->move->up),由于这move的过程中pressed设置为false了,所以不会响应点击事件,如果在500毫秒之内移出了第一个按钮长按事件也不会响应。第二个按钮由于没有获取到down事件,同一个事件序列不会交给它处理,意味着不会执行move以及up事件,所以第二个按钮不会响应点击事件。

你可能感兴趣的:(OnTouchListener、onTouchEvent、onClick的执行顺序)