View事件分发机制2

上一篇算是概述了View事件分发机制。这篇结合源码来具体分析一下里面的一些关键点。以Button为例。

Button有onClick事件 和 onTouch事件,但是onTouch在前,而且如果onTouch返回true,就是消费了点击事件,onClick就不会再执行。

先来看View中的dispatchTouchEvent:(跟源码有点出入,算是简化逻辑,好理解)

public boolean dispatchTouchEvent(MotionEvent event) {

    ......

    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  mOnTouchListener.onTouch(this, event)){  
                return true;  
        }  
        return onTouchEvent(event);  
    } 

    ......

}

可以看到,源码很简单, 如果这个判断条件:

mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)为true,就返回true,否则就去执行onTouchEvent方法。

第一个条件:
通过

public void setOnTouchListener(OnTouchListener l) {  
                mOnTouchListener = l;  
}

可得mOnTouchListener != null 为true。

第二个条件:

(mViewFlags & ENABLED_MASK) == ENABLED表示控件是否是enable,按钮默认都是enable的,所以对于Button来说,这个条件也true.(但是ImageView的这个条件默认是false,也就是说默认不可点击)。

第三个条件:

也就是回调控件注册touch事件的onTouch方法,如果onTouch方法返回true,那么三个条件都是true,就直接返回true,不会再去执行onTouchEvent方法。如果onTouch返回false,那么整个条件就不成立,接下来会去执行onTouchEvent方法。

所以,从onTouch返回true,onClick就不会执行可以知道onClick在onTouchEvent中调用。Button中的onTouchEvent会回调View的onTouchEvent,所以去看View中onTouchEvent的源码:

找到performClick();这个方法

public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}

可以看到,只要mOnClickListener不为null,就会去调用onClick方法。找到Button中的注册接口的方法:

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

当我们调用setOnClickListener注册点击事件时,mOnClickListener不为null,所以就会在找到的performClick中回调onClick方法。

ViewGroup的dispatchTouchEvent:

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        ......

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

        ......

    }

分析可知onInterceptTouchEvent在dispatchTouchEvent中调用.调用的条件是:

一个是在down的时候,另一个就是mFirstTouchTarget!=null。

那么mFirstTouchTarget何时不为空,可以看看ViewGroup中的addTouchTarget这个方法的调用时机,mFirstTouchTarget就是在这里赋值的。

    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
      final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }

mFirstTouchTarget是用来保存ViewGroup中处理事件的子View,也就
是dispatchTouchEvent返回true的子View。所以说,只要有子View的dispatchTouchEvent返回true,mFirstTouchTarget就不会为null。
反之,如果无子View处理,该对象即为null。当然,满足了以上两个条件还不行,还必须要满足 !disallowIntercept。

disallowIntercept这个变量的值主要受FLAG_SPLIT_MOTION_EVENTS
这个标记的影响,这个值可以被ViewGroup的子View设置,子View如果调用了requestDisallowInterceptTouchEvent这个方法,会改变FLAG_SPLIT_MOTION_EVENTS,导致disallowIntercept这个值就是true,这种情况就会跳过intercepted = onInterceptTouchEvent(ev);也
就是拦截失效。

@Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

        if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
            // We're already in this state, assume our ancestors are too
            return;
        }

        if (disallowIntercept) {
            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
        } else {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }

        // Pass it up to our parent
        if (mParent != null) {
            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
        }
    }

但是还没完事儿,FLAG_SPLIT_MOTION_EVENTS这个标记有重置机
制,从ViewGroup中的dispatchTouchEvent源码可以看到:

if (actionMasked == MotionEvent.ACTION_DOWN) {

         cancelAndClearTouchTargets(ev);
         resetTouchState();//重置标记
 }

在处理MotionEvent.ACTION_DOWN的时候会重置这个标记,导致
disallowIntercept为false。所以如果是ACTION_DOWN事件,就一定会执行onInterceptTouchEvent。

所以,如果两个条件都不满足,肯定就不会调用拦截这个方法了。这种情
况一般是在处理完ACTION_DOWN事件后,没有子View来响应后续事件,这种情况下,ViewGroup只能调用自己的onTouchEvent方法自己处
理喽。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值