android进阶篇16、View的事件分发流程机制源码解析

322 阅读6分钟

前言

在分析事件分发机制之前,我们先从广义上了解几个函数的作用

dispatchTouchEvent 这个函数可以认为是事件分发的总入口;

onInterceptTouchEvent 事件拦截的函数,只在ViewGroup中有,view没有此函数;

onTouchEvent 可以认为是事件处理的函数;

dispatchTransformedTouchEvent 事件分发的函数,一般是在dispatchTouchEvent中调用,此函数主要处理两种情况的分发,一种是分发给子view,一种是分发给当前view/viewgroup;

requestDisallowInterceptTouchEvent 请求父viewgroup不要拦截事件,一般在子view中调用,一般的使用方法是在子view的dispatchTouchEvent中调用getParent.requestDisallowInterceptTouchEvent(true);

一、事件分发流程--ViewGroup部分

1、Activity -> dispatchTouchEvent

如下所示,事件分发的入口是从Activity的dispatchTouchEvent方法开始的;

注释1处表示如果是down事件,首先执行onUserInteraction方法,这个方法在Activity中是一个空实现,我们可以在自己的Activity中重写此方法,然后监听down事件,一个应用就是可以在此处埋点统计;

注释2处调用getWindow获得是PhoneWindow,因此实际调用的是phoneWindow的superDispatchTouchEvent;如果注释2处返回为false,也就是事件没有处理,注释3处就会调用Activity的onTouchEvent;

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction(); //1
    }
    if (getWindow().superDispatchTouchEvent(ev)) { //2
        return true;
    }
    return onTouchEvent(ev); //3
}

2、PhoneWindow -> superDispatchTouchEvent

接着看phoneWindow的superDispatchTouchEvent,注释1处表示又调用了DecorView的superDispatchTouchEvent;注释2处表示又调用了ViewGroup的dispatchTouchEvent;

public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event); //1
}

public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event); //2
}

3、ViewGroup -> dispatchTouchEvent

接着看ViewGroup的dispatchTouchEvent,这个方法比较长,包含了主要的分发逻辑;

注释1处表示当事件是点击事件时,会把TouchTarget清空,然后把TouchState重置;

注释2处的disallowIntercept变量通过FLAG_DISALLOW_INTERCEPT标志位设置,那么这个标志位在哪里设置呢?就是在requestDisallowInterceptTouchEvent方法中设置的,当子view调用这个方法请求父view不要拦截时,就不会进入注释3处的onInterceptTouchEvent方法,从而也就不会拦截了;

这里我们可能会有一个疑问,事件分发是从上层分发到下层的,那父view直接把事件拦截了,子view如何去请求父view不要拦截?其实事件分发下来之后down事件一般都是没有拦截的,然后就会进入view的diapatchTouchEvent,我们就可以在这个方法中调用方法请求父view不要拦截,这样当move事件来了之后,因为标志位设置为了true,即使在viewgroup的onInterceptTouchEvent方法中进行了拦截,也不会进入viewgroup的onInterceptTouchEvent方法了,从而不能对事件进行拦截;

注释3处的onInterceptTouchEvent用于父view拦截事件,一般在父viewgroup中不拦截down事件,然后move事件来了之后在这个方法中判断是否需要拦截,如果需要拦截就会返回true,同时发送一个cancel事件给子view,然后后续的move事件不会再继续流向子view了;

注释4和注释5处的dispatchTransformedTouchEvent用于向子view或者自身分发事件;后面会分析;

public boolean dispatchTouchEvent(MotionEvent ev) {
    、、、
    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        // Handle an initial down.
        if (actionMasked == MotionEvent.ACTION_DOWN) {                
            cancelAndClearTouchTargets(ev); //1
            resetTouchState();
        }

        // Check for interception.
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) { //2
                intercepted = onInterceptTouchEvent(ev); //3
                ev.setAction(action);
            } else {
                intercepted = false;
            }
        } else {
            intercepted = true;
        }

        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        if (!canceled && !intercepted) {             

            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {

                final int childrenCount = mChildrenCount;
                if (newTouchTarget == null && childrenCount != 0) {
                    
                    final View[] children = mChildren;
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        、、、
                        //4
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            、、、
                        }
                    }
                    if (preorderedList != null) preorderedList.clear();
                }
            }
        }

        // Dispatch to touch targets.
        if (mFirstTouchTarget == null) {
            // No touch targets so treat this as an ordinary view.
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS); //5
        } else {
            、、、
        }
    }
    return handled;
}

4、ViewGroup -> dispatchTransformedTouchEvent

注释1和注释2处主要处理cancel事件的分发;当child不为null时调用注释2处将cancel事件分发给子view;

注释3和注释4用于正常事件流程的分发;当child为空时,调用注释3处,这里的super就代表的View;当child不为空时,调用注释4处,这里的child如果还是ViewGroup类型,则又会进入ViewGroup的dispatchTouchEvent,继续递归调用分发,如果这个child是View类型的,则会进入View的dispatchTouchEvent;

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
    final boolean handled;

    final int oldAction = event.getAction();
    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
        event.setAction(MotionEvent.ACTION_CANCEL);
        if (child == null) {
            handled = super.dispatchTouchEvent(event); //1
        } else {
            handled = child.dispatchTouchEvent(event); //2
        }
        event.setAction(oldAction);
        return handled;
    }

    // Perform any necessary transformations and dispatch.
    if (child == null) {
        handled = super.dispatchTouchEvent(transformedEvent); //3
    } else {
        、、、

        handled = child.dispatchTouchEvent(transformedEvent); //4
    }

    // Done.
    transformedEvent.recycle();
    return handled;
}

5、ViewGroup -> requestDisallowInterceptTouchEvent

当子view请求viewgroup不要拦截事件时会调用如下方法,注释1处和注释2处分别进行的处理;就是将mGroupFlags变量置为对应的标志位;

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; //1
    } else {
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; //2
    }
}

6、ViewGroup -> onInterceptTouchEvent

ViewGroup的onInterceptTouchEvent方法如下所示,可见viewGroup基本不会拦截事件,正常情况的事件分发基本都是返回false,所以RecyclerView、ScrollView等等这些滚动容器一般都是重写的onInterceptTouchEvent,然后在move事件中判断是否拦截;

public boolean onInterceptTouchEvent(MotionEvent ev) {
    if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
            && ev.getAction() == MotionEvent.ACTION_DOWN
            && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
            && isOnScrollbarThumb(ev.getX(), ev.getY())) {
        return true;
    }
    return false;
}

二、事件分发流程--View部分

1、View -> dispatchTouchEvent

当事件分发到View时,会进入View的dispatchTouchEvent方法,注释1处会首先调用onTouch方法,如果onTouch返回的true,则在注释2处判断result为true,从而不会进入onTouchEvent方法;那么这个onTouch方法是在哪设置的呢?就是调用View的setOnTouchListener方法,注释3所示;

public boolean dispatchTouchEvent(MotionEvent event) {
    、、、

    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // Defensive cleanup for new gesture
        stopNestedScroll();
    }

    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)) { //1
            result = true;
        }

        if (!result && onTouchEvent(event)) { //2
            result = true;
        }
    }
    、、、
    return result;
}

public void setOnTouchListener(OnTouchListener l) {
    getListenerInfo().mOnTouchListener = l; //3
}

2、View -> onTouchEvent

View的onTouchEvent如下所示,我们主要关注performClickInternal方法;

注释1处表示clickable如果为true会进入,这个clickable是通过setOnClickListener方法设置,因此我们如果设置了view的setOnClickListener,就会进入注释2处的performClickInternal方法,在这里面会执行onClick方法,我们在后面会分析;

然后注释3处会返回true代表事件被消费;还有一个需要注意的点,就是performClickInternal方法是在MotionEvent.ACTION_UP分支内执行的,也就是只有up事件才会触发;

public boolean onTouchEvent(MotionEvent event) {      
    、、、
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) { //1
        switch (action) {
            case MotionEvent.ACTION_UP:
                、、、
                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                    // take focus if we don't have it already and we should in
                    // touch mode.
                    、、、

                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                        
                        // 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)) {
                                performClickInternal(); //2
                            }
                        }
                    }                      
                }
                mIgnoreNextUpEvent = false;
                break;

            case MotionEvent.ACTION_DOWN:
                、、、
                break;

            case MotionEvent.ACTION_CANCEL:
                、、、
                break;

            case MotionEvent.ACTION_MOVE:
                、、、
                break;
        }
        return true; //3
    }
    return false;
}

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

3、View -> performClickInternal

View的performClickInternal的调用链如下所示,注释2处我们可以看到最终会调用OnClickListener的onClick方法,也就是我们经常调用的setOnClickListener中传入的listener,进而调用其中的onClick;

private boolean performClickInternal() {
    // Must notify autofill manager before performing the click actions to avoid scenarios where
    // the app has a click listener that changes the state of views the autofill service might
    // be interested on.
    notifyAutofillManagerOnClick();

    return performClick(); //1
}

public boolean performClick() {

    final boolean result;
    final ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        li.mOnClickListener.onClick(this); //2
        result = true;
    } else {
        result = false;
    }
    、、、
    return result;
}