Android事件分发(2)

上一篇主要讲了onTouch和onTouchEvent区别:
1、优先判断onTouch要不要执行
2、如果onTouch执行,返回ture则消费了事件,onTouchEvent不再执行
3、onTouch默认是null的,所以系统源代码 是在onTouchEvent里面识别和处理 点击,滑动,长按等事件的。

以上,是分析 View中dispatchTouchEvent方法的源码得等的结论

这次,看看View的onTouchEvent 里面都做了些什么事情。
Android事件分发机制完全解析,带你从源码的角度彻底理解(上) 中分析的源码是老一点版本的源码,我的是api22中的源码

一、View的onTouchEvent源码分析

    public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
//判断控件是否是 不可用 DISABLED 的,若果是,就返回一个结果。 
//return语句判断了是否是CLICKABLE 或者 LONG_CLICKABLE的,如果是这两者,返回true,消耗掉了事件。换句话说,如果View不可用,并且 View可点击或者可长按,则消费掉事件, 
//这样会 不响应click或者 long click事件。
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (event.getAction() == 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));
        }
//如果当前View设置了TouchDelegate 对象mTouchDelegate,则执行mTouchDelegate 的onTouchEvent事件,根据mTouchDelegate的返回结果,决定要不要继续执行自己的onTouchEvent逻辑。
//Delegate 是代理的意思,简单来说,ViewA 代理了ViewB 
//如果是点击事件,你点击了ViewA ,就和点击了ViewB一样,ViewA 走的都是ViewB的代码判断逻辑。
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

        //View是可点击或可长按的状态。判断为真
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
              //View是可点击或可长按的状态,进入了switch判断
            switch (event.getAction()) {
                case MotionEvent.ACTION_UP:

                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    //判断了按压press状态和焦点状态,
                    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);
                       }
                        //如果只是个tap,非长按事件移除了removeLongPressCallback也就是对于长按事件的检查线程。 
                        if (!mHasPerformedLongPress) {
                            // 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();
                                }
                                //没有直接执行点击performClick ,而是使用post runable的方式执行 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();
                    }
                    break;

                case MotionEvent.ACTION_DOWN:
                //先把执行长按的标记mHasPerformedLongPress 设置为false
                    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 延迟了一小段时间按下事件的检查反馈,延迟事件 getTapTimeout()是100毫秒
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        //没有在一个可滑动的容器内:设置按下状态和位置,马上显示反馈结果(延迟时间为0)

                        setPressed(true, x, y);
                        checkForLongClick(0);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    setPressed(false);
                    removeTapCallback();
                    removeLongPressCallback();
                    break;

                case MotionEvent.ACTION_MOVE:
                //记录位置变化,传递给子view
                    drawableHotspotChanged(x, y);

                    // Be lenient about moving outside of buttons
                    //判断了是否移动到了View以外,如果仍然在View区域内,移除了点击和长按的检查线程
                    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是可点击或可长按的状态,if为真. 
            //可以看到,整个if的最终返回值是true代表处理了此次事件。 
            //也就是说,事件分发到我这个View的时候,如果我这个View是可点击或者可长按的,我就会消费此次事件,停止分发
            return true;
        }
        //如果View是不可点击状态,整个onTouchEvent 会返回false。
        //也就是,我这个View是不可点击也不可长按的,那么我这个View不关心发生了什么事情,我也不做处理。
        return false;
    }

分部分分析和解释:
1、第一个if

if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (event.getAction() == 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));
        }

判断控件是否是 不可用 DISABLED 的,若果是,就返回一个结果。
return语句判断了是否是CLICKABLE 或者 LONG_CLICKABLE的,如果是这两者,返回true,消耗掉了事件。换句话说,如果View不可用,并且 View可点击或者可长按,则消费掉事件
这样会 不响应click或者 long click事件。

2.第二个if,对于TouchDelegate的判断。

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

如果当前View设置了TouchDelegate 对象mTouchDelegate,则执行mTouchDelegate 的onTouchEvent事件,根据mTouchDelegate的返回结果,决定要不要继续执行自己的onTouchEvent逻辑。TouchDelegate 的使用可以看这里

Delegate 是代理的意思,简单来说,ViewA 代理了ViewB
如果是点击事件,你点击了ViewA ,就和点击了ViewB一样,ViewA 走的都是ViewB的代码判断逻辑,并返回结果,根据返回结果判断是否消费事件。

3、第三个大的 if判断是对View 的点击状态进行的判断

        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE))

代码运行到这里,View一定是Enable的状态,此时又进行了,是否可点击状态的判断
A、先分析 if为false的情况,可以看到,如果View是不可点击状态,整个onTouchEvent 会返回false。也就是,我这个View是不可点击也不可长按的,那么我这个View不关心发生了什么事情,我也不做处理。

B、if为真,View是可点击或可长按的状态。
可以看到,整个if的最终返回值是true代表处理了此次事件。
也就是说,事件分发到我这个View的时候,如果我这个View是可点击或者可长按的,我就会消费此次事件,停止分发

C、if为true,进入了switch判断。
①case MotionEvent.ACTION_UP:
判断了按压press状态和焦点状态,如果只是个tap,移除了removeLongPressCallback也就是对于长按事件的检查线程。
没有直接执行点击performClick ,而是使用post runable的方式执行 performClick

②case MotionEvent.ACTION_DOWN:
先把执行长按的标记mHasPerformedLongPress 设置为false

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

为了防止这个是滑动的操作,postDelayed 延迟了一小段时间按下事件的检查反馈,延迟事件 getTapTimeout()是100毫秒

没有在一个可滑动的容器内:

else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true, x, y);
                        checkForLongClick(0);
                    }

设置按下状态和位置,马上显示反馈结果(延迟时间为0)

③case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);记录位置变化,传递给子view

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

判断了是否移动到了View以外,如果仍然在View区域内,移除了点击和长按的检查线程


以上就是View的onTouchEvent的源码分析,有些乱,但大体都解释到了

二、View的onTouchEvent的总结

  1. View的onTouchEvent首先判断了View是否是可用的。
    A.不可用但可点击,返回true,消费了事件
    B.不可用但可长按,返回true,消费了事件

  2. View是可用的状态,进行了View是否设置了TouchDelegate 的判断
    A. 如果设置了TouchDelegate ,则执行TouchDelegate 对象的onTouchEvent方法,根据返回值决定要不要继续执行

  3. View 是可用状态,可点击或者可长按的状态,对MotionEvent对象进行判断。
    A. 在MotionEvent.ACTION_UP 动作中,进行是长按还是点击的判断,并执行点击performClick()
    B. MotionEvent.ACTION_DOWN动作中,判断了是否是一个可滑动容器
    C.MotionEvent.ACTION_MOVE动作中,判断了是否仍在View区域内

三、performClick()代码逻辑

    /**
     * Call this view's OnClickListener, if it is defined.  Performs all normal
     * actions associated with clicking: reporting accessibility event, playing
     * a sound, etc.
     *
     * @return True there was an assigned OnClickListener that was called, false
     *         otherwise is returned.
     */
    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不为null,然后执行setOnClickListener 设置的点击事件。

    public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }

而且可以看到,在setOnClickListener 里面,已经把View设置为Clickable可点击的了。


以上就是View的onTouchEvent执行的逻辑。

猜你喜欢

转载自blog.csdn.net/kangyouv/article/details/76560780