Android事件分发机制深度分析(四)

本文详细解析了View的dispatchTouchEvent源码,探讨了View如何处理点击事件,包括OnTouchListener与onTouchEvent的关系、不可用状态下的事件处理、代理View的事件处理以及CLICKABLE、LONG_CLICKABLE和CONTEXT_CLICKABLE标志的影响。同时,分析了ACTION_UP事件触发performClick方法及OnClickListener的调用过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

View事件分发机制的源码解析

ViewGroup不论是拦截事件或者将事件分发给子View,其最终都是调用了View的dispatchTouchEvent方法,毕竟ViewGroup也是继承自View。所以下面分析View#dispatchTouchEvent源码来了解View事件分发机制。

    public boolean dispatchTouchEvent(MotionEvent event) {
        ...

        boolean result = false;
        ...

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

            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

        ...

        return result;
    }

View对点击事件的处理过程就比较简单了,因为View中没有子元素无法向下传递事件,所以View是所有事件传递的最终端,只能自己处理事件。
从上面的源码中可以看出View对点击事件的处理过程,首先会判断是否设置了OnTouchListener,如果OnTouchListener中的onTouch方法返回true,那么onTouchEvent就不会被调用,可见OnTouchListener的优先级高于onTouchEvent,这样做的好处是方便在外界处理点击事件。

接着再分析onTouchEvent的实现。先看当View处于不可用状态下点击事件的处理过程,如下所示。不可用状态下的View照样会消耗点击事件,尽管它看起来不可用。

        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;
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return clickable;
        }

接着,如果View设置有代理,那么当前View的点击事件会交给代理的View来处理,调用代理View的onTouchEvent方法,如果代理View消耗了事件,那么相当于当前View消耗了事件。

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

接下来便是onTouchEvent中对点击事件的具体处理

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

if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
    switch (action) {
        case MotionEvent.ACTION_UP:
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
            ...
                boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
            if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                ...
                    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();
                            }
                        }
                    }

                ...

                    removeTapCallback();
            }
            mIgnoreNextUpEvent = false;
            break;

        case MotionEvent.ACTION_DOWN:
            ...
                break;

        case MotionEvent.ACTION_CANCEL:
            ...
                break;

        case MotionEvent.ACTION_MOVE:
            ...
                break;
    }

    return true;
}

可以看出当CLICKABLE或者LONG_CLICKABLE以及CONTEXT_CLICKABLE中一个或多个标志被设置时,onTouchEvent返回true,表示消耗这个事件,不论当前View是否是DISABLE状态。当ACTION_UP事件发生时,会触发 performClick 方法,如果 View 设置了 OnClickListener,那么 performClick 方法内部会调用它的onClick方法,如下所示:

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

        notifyEnterOrExitForAutoFillIfNeeded(true);

        return result;
    }

View的LONG_CLICKABLE属性默认为false, 而CLICKABLE属性是否为false和具体的View有关,确切来说是可点击的View其CLICKABLE为true, 不可点击的View其
CLICKABLE为 false, 比如Button是可点击的,TextView是不可点击的。通过setClickable和setLongClickable以及setContextClickable可以分别改变View 的CLICKABLE和 LONG_CLICKABLE以及属性。另外,setOnClickListener 会自动将 View的CLICKABLE设为 true, setOnLongClickListener则会自动将View的LONG_CLICKABLE设为true,setOnContextClickListener会自动将 View的CONTEXT_CLICKABLE设为 true,这一点从源码中可以看出来,如下所示。

    public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }
    public void setOnLongClickListener(@Nullable OnLongClickListener l) {
        if (!isLongClickable()) {
            setLongClickable(true);
        }
        getListenerInfo().mOnLongClickListener = l;
    }
    public void setOnContextClickListener(@Nullable OnContextClickListener l) {
        if (!isContextClickable()) {
            setContextClickable(true);
        }
        getListenerInfo().mOnContextClickListener = l;
    }

View的事件分发主要是对事件的具体处理,上面主要分析ACTION_UP点击事件的处理流程,其它点击事件可参照源码具体分析。同样对View的事件分发总结为一张流程图,如下:
在这里插入图片描述

Android事件分发机制深度分析(一)
Android事件分发机制深度分析(二)
Android事件分发机制深度分析(三)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值