View的事件分发拦截

本文详细解析了Android中View的事件分发机制,包括dispatchTouchEvent、onInterceptTouchEvent及onTouchEvent的方法作用及其交互流程。同时深入探讨了ViewGroup与View在事件分发中的差异,并通过源码解读了事件分发的具体实现。
摘要由CSDN通过智能技术生成

View的事件分发机制

1.view的点击事件原理

View的点击事件的分发,即对MotionEvent事件的分发,当一个MotionEvent产生后,系统需要把该事件传递到一个具体的View进行处理,该过程即为View事件的分发,在MotionEvent的传递过程中,需要经历三个重要的方法:dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent。

dispatchTouchEvent
该方法主要对事件进行分发,当时间传递到当前view时,该方法会被调用。

onInterceptTouchEvent
该方法用来判断是否拦截某个事件,如果当前view拦截了某个事件,那么在同一事件序列中,该方法不会再次被调用。

onTouchEvent
该方法用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则无法再接收后续的事件。

其中三者之间的关系如下所示:

            public boolean dispatchTouchEvent(MotionEvent ev) {
                 boolean consume = false;
                 if(onInterceptTouchEvent(ev)){
                    consume = onTouchEvent(ev);
                 }else{
                    consume = child.dispatchTouchEvent(ev);
                 }
            }

即在一次事件分发的过程中,判断当前view(viewGroup)是否拦截,如果拦截,则交给onTouchEvent进行处理,如果不拦截,则交给子view的dispatchTouchEvent处理,至此完成一轮事件的分发。


注:同一事件序列事件是指从MotionEvent.ACTION_DOWN开始+中间经历的ACTION_MOVE到最后ACTION_UP的过程中的所有事件。


2.由源码验证iew的事件原理

ViewGroup对事件的分发

首先查询VIewGroup源码中对事件分发过程,我们发现这样一段代码:

    // Check for interception.
    final boolean intercepted;
    if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {
                intercepted = false;
            }
    } else {
        // There are no touch targets and this action is not an initial down
        // so this view group continues to intercept touches.
        intercepted = true;
    }

首先分析if里边的内容

    if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null)

其中的mFirstTouchTarget暂且理解为当前点击的位置的子View,即能接收此点击事件的view。当子元素处理了点击事件后,就会为mFirstTouchTarget进行赋值。
当一个事件到来时,首先判断是否为ACTION_DOWN,如果为ACTION_DOWN那么肯定走if为true时的内容,当不是ACTION_DOWN时,即后续的事件到来时,就会根据第一次处理的结果进行判断,如果第一次即ACTION_DOWN事件被当前view处理了,那么mFirstTouchTarget肯定为null,则直接拦截后续的事件交给onInterceptTouchEvent进行处理,如果ACTION_DOWN事件被子元素处理了,那么mFirstTouchTarget不为null,继续进行后续的判断看是否拦截。

    if (!disallowIntercept) {
        intercepted = onInterceptTouchEvent(ev);
        ev.setAction(action); // restore action in case it was changed
    }

由disallowIntercept我们可以理解为是否禁止拦截事件,而该变量值有FLAG_DISALLOW_INTERCEPT进行控制,如果不通过FLAG_DISALLOW_INTERCEPT进行干预,那么disallowIntercept为false,然后会调用onInterceptTouchEvent看该事件是否被拦截。如果拦截,那么onInterceptTouchEvent返回true,由三者的关系图我们可以知道,当intercepted为true时,将会调用onTouchEvent对事件进行处理。如果不拦截,则intercepted为false,交给子view的dispatchTouchEvent进行处理。

也就是当ACTION_DOWN到来时,肯定会调用onInterceptTouchEvent进行判断是否拦截,如果拦截,后续的事件都会交给该viewGroup进行处理,不会再经过子元素,如果不拦截,交给子元素处理。
当后续事件到来时,如果第一次ACTION_DOWN进行了拦截,mFirstTouchTarget==null,那么之后的所有事件都会交给当前viewGroup进行处理,不会再分发到子元素。如果第一次ACTION_DOWN事件没有拦截,被子元素进行处理了,mFirstTouchTarget!=null,但是子元素只要不对父viewGroup进行干扰,即将disallowIntercept设置为true,那么这些后续事件还是会经过viewGroup的onInterceptTouchEvent询问是否拦截处理。如果不处理再交给子元素。

disallowIntercept决定了viewGroup是否拦截事件,如果在子元素中将其设置为true,那么后续的所有事件都不会再经过viewGroup中的onInterceptTouchEvent进行判断。在子元素中通过如下方法设置该值。

    parent.requestDisallowInterceptTouchEvent(true);

同时由如下代码我们可以知道,当每次ACTION_DOWN到来时,会重置之前的所有状态,即将mFirstTouchTarget置为null。

// Handle an initial down.
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // Throw away all previous state when starting a new touch gesture.
        // The framework may have dropped the up or cancel event for the previous gesture
        // due to an app switch, ANR, or some other state change.
        cancelAndClearTouchTargets(ev);
        resetTouchState();
    }
View对事件的分发

而view对事件的分发则相对简单一些,其中这里的view不包括viewgroup。

    if (onFilterTouchEventForSecurity(event)) {
        //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;
        }
    }

view会进行判断看是否设置了OnTouchListener,如果设置了则会返回true,onTouchEvent事件就不会被调用,如果没有设置,才会调用onTouchEvent事件。

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

在onTouchEvent中,首先会判断当前view是否可用,如果不可用,也会消耗点击事件,只是看起来没效果而已。
如果可用,则会判断是否可点击,即接收CLICKABLE和LONG_CLICKABLE是否为true。

    if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
        //do something
        ........
        if (!post(mPerformClick)) {
            performClick();
        }
        ........
    }

如上边代码所示,只要CLICKABLE和LONG_CLICKABLE有一个为true,则会消耗这个事件。其中performClick()方法中会调用onClick方法。

由此可见,在事件的优先级中onTouchListener(onTouch)>onTouchEvent>onClick;

view的LONG_CLICKABLE默认为false,而CLICKABLE则跟具体的view有关,例如Button的CLICKABLE默认为true,而TextView的默认为false,即跟该view是否可点击有关,但是我们在对TextView设置点击事件时并没有专门设置该属性,为什么也能响应点击事件呢?

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

如上边所示,在view的源码中,当对view设置点击事件时,会首先判断isClickable,并将其设置为true,因此不用我们再手动进行设置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值