Android事件分发(2)

来源:互联网 发布:学生双肩包推荐 知乎 编辑:程序博客网 时间:2024/05/29 14:31

上一篇主要讲了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执行的逻辑。

原创粉丝点击