安卓事件分发学习之onTouchEvent方法

背景

在安卓事件分发学习之onInterceptTouchEvent方法一文中,我记录了事件分发流程里第二个方法onInterceptTouchEvent()的源码阅读过程,现在记录一下最后一个方法onTouchEvent()的阅读

在文章安卓事件分发学习之dispatchTouchEvent方法中,可以看到onTouchEvent()方法的执行优先级是View->ViewGroup->Activity,那我就按着这个顺序来阅读此方法

其实在阅读的时候发现,ViewGroup并没有覆写onTouchEvent,所以它执行的,还是View类的onTouchEvent。

所以只看View.onTouchEvent()和Activity.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) {
            // 如果当前view是disable状态
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false); // action_up事件,并且pressed标志位不是0,就设置pressed为false
            }
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; // 清除标志位
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return clickable; // 如果一个view是disable但可点击,就仍然消费这个事件流,但不响应
        }

        // 设置touch代理的话,调用touch代理的onTouchEvent(),并且返回true,确定消费事件流
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            // 只考虑clickable的情况,不是clickable,绝对返回false
            switch (action) {
                case MotionEvent.ACTION_UP:
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    if ((viewFlags & TOOLTIP) == TOOLTIP) {
                        handleTooltipUp();
                    }
                    if (!clickable) {
                        // 移除pressed标志位和mPendingCheckForTap回调,但是一般情况这个!clickable判断不成立
                        removeTapCallback();
                        removeLongPressCallback();
                        mInContextButtonPress = false;
                        mHasPerformedLongPress = false;
                        mIgnoreNextUpEvent = false;
                        break;
                    }
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    // 是否设置了prePressed或是否按下pressed
                    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()) {
                            // 当前view或子view是否有焦点,有的话,不会执行onClick()
                            focusTaken = requestFocus();
                        }

                        if (prepressed) {
                            setPressed(true, x, y);
                            // action_up的时候才会setPressed()
                        }

                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // 没有执行onLongClick()并且不是多点触摸的话(mIgnoreNextUpEvent似乎在多点触摸的情况下,才可能为true)
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback(); // 移除监听

                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                                // 当前view或子view没有焦点,才会执行onClick()
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    // action_up的时候才会调用onClick()
                                    performClick();
                                }
                            }
                        }

                        // UnsetPressedState类唯一的方法--run()的唯一操作就是setPressed(false)
                        // 这里的目的当然就是设置pressed为false

                        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();
                        }

                        // 移除pressed标志位和mPendingCheckForTap回调
                        removeTapCallback();
                    }
                    mIgnoreNextUpEvent = false;
                    break;

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

                    if (!clickable) {
                        // 当前view不是clickable,就尝试执行onLongClick()
                        checkForLongClick(0, x, y); // 但一般情况下,这个判断不会成立
                        break;
                    }

                    if (performButtonActionOnTouchDown(event)) {
                        // 这个方法一般返回false
                        break;
                    }

                    // Walk up the hierarchy to determine if we're inside a scrolling container.
                    boolean isInScrollingContainer = isInScrollingContainer();
                    // 三大布局这里都是false

                    // For views inside a scrolling container, delay the pressed feedback for
                    // a short period in case this is a scroll.
                    // 根据父view能不能scroll决定是不是尝试执行onLongClick
                    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(0, x, y);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    // 移除回调和标志位回置
                    if (clickable) {
                        setPressed(false);
                    }
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    break;

                case MotionEvent.ACTION_MOVE:
                    if (clickable) {
                        // 当前view可点击的话,重新画热区
                        drawableHotspotChanged(x, y);
                    }

                    // Be lenient about moving outside of buttons
                    // 如果移到了view外面,进行回调的移除和标志位的回置,setPressed为false
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        // Remove any future long press/tap checks
                        removeTapCallback();
                        removeLongPressCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            setPressed(false);
                        }
                        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    }
                    break;
            }

            return true;
        }

        return false;
    }

代码虽长,逻辑却算不得复杂

得出来的最重要的几个结论就是:

1、onClick、onLongClick优先级是小于onTouchEvent()的,同时结合前一篇文章,可以得到优先级如下:onTouch()、onTouchEvent()、onLongClick()、onClick()

2、onClick是在action_up里执行的,onLongClick是在action_down里执行的。而且如果onLongClick返回true,onClick不会执行

3、有焦点的时候是不会执行onClick的


可以参加下面的源码记录

longClick

在action_down里面,调用了一个重要方法:checkForLongClick,源码如下

    private void checkForLongClick(int delayOffset, float x, float y) {
        if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
            mHasPerformedLongPress = false; // 这个标志位决定了onClick是否在action_up时调用

            if (mPendingCheckForLongPress == null) {
                mPendingCheckForLongPress = new CheckForLongPress();
            }
            mPendingCheckForLongPress.setAnchor(x, y);
            mPendingCheckForLongPress.rememberWindowAttachCount();
            mPendingCheckForLongPress.rememberPressedState();
            postDelayed(mPendingCheckForLongPress,
                    ViewConfiguration.getLongPressTimeout() - delayOffset);
        }
    }

如果是long_clickable的话,就post一个runnable类对象--CheckForLongPress类,此类源码如下

    private final class CheckForLongPress implements Runnable {
        private int mOriginalWindowAttachCount;
        private float mX;
        private float mY;
        private boolean mOriginalPressedState;

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

        public void setAnchor(float x, float y) {
            mX = x;
            mY = y;
        }

        public void rememberWindowAttachCount() {
            mOriginalWindowAttachCount = mWindowAttachCount;
        }

        public void rememberPressedState() {
            mOriginalPressedState = isPressed();
        }
    }

最主要的是run()方法,正常情况下,run()方法里第一个判断会成立,那就会执行performLongClick(),它的返回值为true,mHasPerformLongPress就为true,反之为false

performLongClick()方法直接调用了performLongClickInternal()方法,源码如下

    private boolean performLongClickInternal(float x, float y) {
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);

        boolean handled = false;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnLongClickListener != null) {
            handled = li.mOnLongClickListener.onLongClick(View.this);
        }
        if (!handled) {
            final boolean isAnchored = !Float.isNaN(x) && !Float.isNaN(y);
            handled = isAnchored ? showContextMenu(x, y) : showContextMenu();
        }
        if ((mViewFlags & TOOLTIP) == TOOLTIP) {
            if (!handled) {
                handled = showLongClickTooltip((int) x, (int) y);
            }
        }
        if (handled) {
            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
        }
        return handled;
    }

可见,最主要的就是我们的监听器的onLongClick()方法,它为true,就铁定返回true,onClick()就不会执行,它为false,一般情况下(没考虑上下文菜单),onClick才会执行


Activity#onTouchEvent

如果一个事件流没有一个view处理,那就会走到Activity.onTouchEvent()中,这个方法代码如下

    public boolean onTouchEvent(MotionEvent event) {
        if (mWindow.shouldCloseOnTouch(this, event)) {
            /*
            public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
                if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
                        && isOutOfBounds(context, event) && peekDecorView() != null) {
                    return true; // 超过了边界而且是down事件,返回true
                }
                return false;
            }
             */
            finish();
            return true;
        }

        return false;
    }

这里返回true或false已经无所谓了,反正事件流也到头了。


结语

安卓事件分发源码大体就这样了,源码来自sdk-26

你可能感兴趣的:(安卓开发,Android事件分发)