Android事件分发05——View的onTouchEvent

Android事件分发05——View的onTouchEvent

上次我们分析了view的dispatchTouchEventAndroid事件分发04——View的dispatchTouchEvent ),说到事件分发到view的dispatchTouchEvent后,会先到触摸监听的onTouch中,然后再到view的onTouchEvent中。现在我们一起来看看onTouchEvent

一、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);
    }

    //如果设置了Touch的代理,那么直接调用代理的 onTouchEvent,如下消费了,那么返回true
    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }

    //控件可点击的(包括点击、长按等)才操作,操作完成,直接返回true表示消费了
    if (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
            (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                //获取是否具有 短暂延反馈的情况,就是down中包含在滚动控件中的情况,这个就是去除了滚动的情况
                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);
                   }

                   //没有长按并且mIgnoreNextUpEvent==false
                    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) {//如果按下了
                        //添加一个延时任务,这个任务把按下标记设为false
                        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.
                //获取控件是否包含在可滚动的view中
                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);//按下设为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;
}

二、动作处理之前

动作处理之前 : 2—25行

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

    //如果设置了Touch的代理,那么直接调用代理的 onTouchEvent,如下消费了,那么返回true
    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }

这里面首先判断空寂是否不可用,不可用的情况下,我们还是会消费消费事件的,只是不响应而已。

如果不满足上面的情况,我们接着判断这个控件有没有添加了触摸的代理,如果添加了,那么我们直接调用代理的onTouchEvent方法,如果这个方法消费了事件,那么直接放回true。负责继续往下走,走入到action处理的核心。

三、按下的判断

对应:28–151行

    //控件可点击的(包括点击、长按等)才操作,操作完成,直接返回true表示消费了
    if (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
            (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {

            ....

        return true;
    }

这里面判断是否可以点击,只有可以点击才处理,进入后,处理完成直接返回true,代表消费了。

四、MotionEvent.ACTION_DOWN

case MotionEvent.ACTION_DOWN:
    mHasPerformedLongPress = false;

    if (performButtonActionOnTouchDown(event)) {
        break;
    }

    // Walk up the hierarchy to determine if we're inside a scrolling container.
    //获取控件是否包含在可滚动的view中
    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;

4-6行:这里面根据performButtonActionOnTouchDown方法的返回值判断是否对down动作还要处理,那么我们看看performButtonActionOnTouchDown方法

  protected boolean performButtonActionOnTouchDown(MotionEvent event) {
        if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE &&
            (event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) {
            showContextMenu(event.getX(), event.getY(), event.getMetaState());
            mPrivateFlags |= PFLAG_CANCEL_NEXT_UP_EVENT;
            return true;
        }
        return false;
    }

这个方法处理的是,鼠标的动作。是鼠标,并且点击了右键,才返回true。因此我们可以知道我们这里面放回的false。


第10行:boolean isInScrollingContainer = isInScrollingContainer(); 调用了 isInScrollingContainer()方法

 public boolean isInScrollingContainer() {
        ViewParent p = getParent();
        while (p != null && p instanceof ViewGroup) {
            if (((ViewGroup) p).shouldDelayChildPressedState()) {
                return true;
            }
            p = p.getParent();
        }
        return false;
    }

这里面的处理,其实就是获取不断的获取父控件,判断父控件是否具有滚动的功能。

    /**
     * Return true if the pressed state should be delayed for children or descendants of this
     * ViewGroup. Generally, this should be done for containers that can scroll, such as a List.
     * This prevents the pressed state from appearing when the user is actually trying to scroll
     * the content.
     *
     * The default implementation returns true for compatibility reasons. Subclasses that do
     * not scroll should generally override this method and return false.
     */
    public boolean shouldDelayChildPressedState() {
        return true;
    }

15–23行:如果这个控件包含在可滚动的控件内,我们需要延迟反馈。延迟100毫秒,后执行 mPendingCheckForTap 任务。

ViewConfiguration

private static final int TAP_TIMEOUT = 100;//毫秒
public static int getTapTimeout() {
    return TAP_TIMEOUT;
}

View

 private final class CheckForTap implements Runnable {
        public float x;
        public float y;

        @Override
        public void run() {
            mPrivateFlags &= ~PFLAG_PREPRESSED;
            setPressed(true, x, y);
            checkForLongClick(ViewConfiguration.getTapTimeout());
        }
    }

这里面我们来看看checkForLongClick

 private void checkForLongClick(int delayOffset) {
        //是否具有长按标记
        if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
            mHasPerformedLongPress = false;

            if (mPendingCheckForLongPress == null) {
                mPendingCheckForLongPress = new CheckForLongPress();
            }
            //编辑window添加的次数
            mPendingCheckForLongPress.rememberWindowAttachCount();
            //延时执行任务  500-100=400ms
            postDelayed(mPendingCheckForLongPress,
                    ViewConfiguration.getLongPressTimeout() - delayOffset);
        }
    }

这里面我们看看
CheckForLongPress

    private final class CheckForLongPress implements Runnable {
        private int mOriginalWindowAttachCount;

        @Override
        public void run() {
        //是按下的,父控件部位空,自己标记的和view的添加的次数相同
            if (isPressed() && (mParent != null)
                    && mOriginalWindowAttachCount == mWindowAttachCount) {
                if (performLongClick()) {
                    mHasPerformedLongPress = true;
                }
            }
        }

        public void rememberWindowAttachCount() {
            mOriginalWindowAttachCount = mWindowAttachCount;
        }
    }

现在到了 performLongClick

  public boolean performLongClick() {
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);

        boolean handled = false;
        ListenerInfo li = mListenerInfo;
        //监听不为空,使用监听来处理事件
        if (li != null && li.mOnLongClickListener != null) {
            handled = li.mOnLongClickListener.onLongClick(View.this);
        }
        //如果没有消费事件,使用showContextMenu来处理
        if (!handled) {
            handled = showContextMenu();
        }
        if (handled) {
            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
        }
        return handled;
    }

23–28行:如果没有被包含在可滚动的控件中,那么直接显示反馈

总结按下的处理:其实就是被包含在滚动控件中,那么延时按下的返回,反之,立即反馈。

五、MotionEvent.ACTION_MOVE

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;

这里面的操作,其实就是,如果点没有落在view上,那么取消我们的处理。负责不操作。
pointInView在以后的文章中分析

六、MotionEvent.ACTION_UP

case MotionEvent.ACTION_UP:
    //获取是否具有 短暂延反馈的情况,就是down中包含在滚动控件中的情况,这个就是去除了滚动的情况
    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);
       }

       //没有长按并且mIgnoreNextUpEvent==false
        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) {//如果按下了
            //添加一个延时任务,这个任务把按下标记设为false
            postDelayed(mUnsetPressedState,
                    ViewConfiguration.getPressedStateDuration());
        } else if (!post(mUnsetPressedState)) {
            // If the post failed, unpress right now
            //任务调用失败,直接执行这个任务
            mUnsetPressedState.run();
        }
        //移除敲击事件
        removeTapCallback();
    }
    mIgnoreNextUpEvent = false;
    break;

这里面注释已经很详细了。主要看看一哈22–40行:

22行:mHasPerformedLongPress 这个参数,我们在LongPress的延迟处理中赋过值(CheckForLongPress ),也即是说,如果发生了长按这个值为true。

33–35行:这里面涉及到了PerformClick这个类,我们来看看

PerformClick

private final class PerformClick implements Runnable {
    @Override
    public void run() {
        performClick();
    }
}

这个看到这个里面直接执行performClick()

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

performClick方法里面我们可以看到,主要就是判断我们有没有添加监听,如果添加了监听,调用监听的onClickli.mOnClickListener.onClick(this);)并且返回true,代表消费了事件,否则返回false。

36–38行:
这里if判断,主要是判断这个任务有没有添加到消息队列中去了,如果没有添加成功,那么直接调用performClick。我们可以看看 post方法

    /**
     * <p>Causes the Runnable to be added to the message queue.
     * The runnable will be run on the user interface thread.</p>
     *
     * @param action The Runnable that will be executed.
     *
     * @return Returns true if the Runnable was successfully placed in to the
     *         message queue.  Returns false on failure, usually because the
     *         looper processing the message queue is exiting.
     *
     * @see #postDelayed
     * @see #removeCallbacks
     */
    public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
        // Assume that post will succeed later
        ViewRootImpl.getRunQueue().post(action);
        return true;
    }

方法上说的很清楚了:如果Runnable成功添加到消息队列中返回true。失败返回false。

小小的总结:我们对up动作小小的总结一下,主要就是判断有没有按下,然后判断要不要执行onClick方法。

七、MotionEvent.ACTION_CANCEL

case MotionEvent.ACTION_CANCEL:
    setPressed(false);//按下设为false
    removeTapCallback();//取消敲击
    removeLongPressCallback();//取消长按
    mInContextButtonPress = false;
    mHasPerformedLongPress = false;
    mIgnoreNextUpEvent = false;
    break;

这个动作中,其实就是恢复一些初始化状态。

八、流程图

通过上面的分析,我们来个流程图总结一下。
这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值