事件分发常见Interview questions

1.ViewGroup中的mFirstTouchTarget是一个什么东西,它有什么作用?

在ViewGroup中有一个类型为TouchTrarget的mFirstTouchTarget的成员变量,它是用来保存消费事件的子View的信息的。代码如下:

private static final class TouchTarget {
        @UnsupportedAppUsage
        public View child;
        public TouchTarget next;
       // ...省略无关代码
    }

可以看到TouchTarget内部保存了一个View和一个类型为TouchTarget的next成员变量,也就是说TouchTarget是一个链表结构。为什么是链表结构呢?主要是因为Android系统是支持多点触控的,所以TouchTarget设计成了链表。

设计mFirstTouchTarget的目的是为了避免在所有的事件序列中都去递归查找要消费事件的View,只需要在ACTION_DOWN中递归查找消费的View,并将View封装后赋值为mFirstTouchTarget,避免了后续一系列事件的查找。

mFirstTouchTarget会在ACTION_DOWN的时候被赋值,查找是否有能够消费事件的子View,如果有则将这个View包装成TouchTarget赋值给mFirstTouchTarget,否则mFirstTouchTarget为null。

接下来的一系列ACTION_MOVE事件都会根据mFirstTouchTarget是否为null和onInterceptTouchEvent来判断是否要拦截事件。所以mFirstTouchTarget在事件分发的流程中占了非常重要的作用。

2.如果在ViewGroup中拦截了ACTION_DOWN事件会怎样?

首先来看ViewGroup的dispatchTouchEvent方法的伪代码:

// ViewGroup
public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean handled = false;
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
             // 调用自身的onInterceptTouchEvent方法来判断是否拦截事件
            intercepted = onInterceptTouchEvent(ev);
        } else {
            intercepted = true;
        }
        // 如果在ACTION_DOWN中拦截了事件,那么intercepted恒为true,则无法给mFirstTouchTarget赋值
        if (!canceled && !intercepted) {
            mFirstTouchTarget = findConsumeChild(this);
        }
        if (mFirstTouchTarget == null) {  
            // 则调用自身的dispatchTouchEvent方法分发事件             
            handled = super.dispatchTouchEvent(event);
        } else{
            // 则调用子View的dispatchTouchEvent方法
            handled = mFirstTouchTarget.child.dispatchTouchEvent(event);
        }
        return handled;
    }

从上面的代码中可以看到,如果在onInterceptTouchEvent方法中拦截ACTION_DOWN事件,则intercepted为true,而intercepted为true直接导致了mFirstTouchTarget无法被赋值。

接下来,一系列ACTION_MOVE以及ACTION_UP等事件都无法再调用onInterceptTouchEvent方法,也就是intercepted恒为true,且mFirstTouchTarget恒为null。

再往下,由于mFirstTouchTarget恒为null,就导致了所有的Motion事件(也包括ACTION_DOWN事件)只能交由自身处理,无法再将事件分发给子View。

这一点在事件分发流程中非常重要,通过Down事件确定了要处理该事件的View,接下来所有的Move事件序列就不会再走递归,而是直接交给这个View来

3.为什么设置了onTouchListener后onClickListener不会被调用?

// View
public boolean dispatchTouchEvent(MotionEvent 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;
            }
        }

        return result;
    }

在View的dispatchTouchEvent中如果li.mOnTouchListener不为null,则调用li.mOnTouchListener.onTouch,而如果li.mOnTouchListener.onTouch返回了true,则下边的onTouchEvent就不会被调用,而onClickListener就是在onTouchEvent方法中才被调用的。

// 伪代码
public boolean onTouchEvent(MotionEvent event) {
    public boolean onTouchEvent(MotionEvent event) {
        case MotionEvent.ACTION_UP:
            li.mOnClickListener.onClick(this);
        break;
    }
}

4.为什么一个View设置了setOnTouchListener会有提示没有引用performClick方法的警告?

当你添加了一些点击操作,例如像setOnClickListener这样的,它会调用performClick才可以完成onClick方法的调用,但你重写了onTouch,就有可能使得performClick没有被调用,这样这些点击操作就没办法完成了,所以就会有了这个警告。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容