事件分发

事件分发的对象

首先我们要清楚,事件分发的对象是什么?其实就 MotionEvent,这个 MotionEvent 可以有 ACTION_DOWN,ACTION_UP,ACTION_MOVE,ACTION_CANCEL 等事件类型。

事件(MotionEvent)分发是在哪些对象中进行的?

Activity -> Window -> ViewGroup -> View

重点关注的方法

dispatchTouchEvent

onInterceptTouchEvent

onTouchEvent

ACTION_DOWN 事件在 Activity、ViewGroup、View 中根据不同函数不同的返回值的走向?

关于事件的走向,我觉的以下这张图可以很清晰的看出事件的最终走向,该图来自Kelin

image

结论

dispatchTouchEvent 和 onTouchEvent 中返回 true,ACTION_DOWN 事件就在此终止,不会再往上传也不会往下传了。

dispatchTouchEvent 和 onTouchEvent 中返回 false,ACTION_DOWN 事件交给父控件的 onTouchEvent 进行处理

onInterceptTouchEvent 一旦返回 true,那么 ViewGroup 之后不会再调用 onInterceptTouchEvent

事件分发源码分析

Activity

一旦事件产生,那么首先被调用的是 Activity 中的 dispatchTouchEvent 方法。我们来看看这个方法的实现。

image

首先,如果是 ACTION_DOWN,那么 onUserInteraction 方法就会被调用,onUserInteraction 是个空的方法,当事件产生时,那么这个方法就会被调用,如果在 activity 运行的时候,我们想要知道用户和设备的交互,那么我们就可以实现这个方法。

接着 window 中的 superDispatchTouchEvent 方法被调用,事件传递到 window 中。Window 是个抽象类,他的唯一实现是 PhoneWindow。在 Window.superDispatchTouchEvent 中调用了 DecorView 的 superDispatchTouchEvent,而这个 DecorView 就是我们在 activity 中通过调用 setContentView 设置的布局的顶层 View。

image

最后,如果 Activity 中的所有下级事件承载对象没有处理事件,最后 Activity 中的 onTouchEvent 就会被调用,当事件超出边界或者事件为 ACTION_DOWN 时,mWindow.shouldCloseOnTouch(this, event) 为 true,onTouchEvent 默认是返回 false 的。

image

onTouchEvent 方法

当所有的 view 都没有消费事件的时候,activity 的 onTouchEvent() 就会被调用

我们这边看一下 getWindow() 里面的实现,其实也就是返回一个 Window 实例

image

Window(PhoneWindow)

Window 在事件分发的过程中就类似于一个中间的桥接一样,是没有做什么操作的,只是将事件传递给 DecorView 中。

image

ViewGroup

要理解 ViewGroup 对于事件的处理,我们先来个简化的代码逻辑来帮助我们理解

image

对于一个 ViewGroup 来说,点击事件产生之后,dispatchTouchEvent 就会被调用,如果这个 ViewGroup 的 onInterceptTouchEvent 返回 true 就表示它要拦截当前的事件,接着事件就会给这个 ViewGroup 处理,即它的 onTouchEvent 会被调用;如果 ViewGroup 的 onInterceptTouchEvent 返回 false,就表示它不拦截事件,这时当前事件就会被传递给它的子 view,接着调用子元素的 dispatchTouchEvent 方法就会被调用,如此反复,直到事件被最终处理。

大致流程图

image

我们再看看 ViewGroup.dispatchTouchEvent() 还原度比较高的源码

            // 省略部分代码

            // 如果是 ACTION_DOWN 事件,重置标志位 mGroupFlags 为 非 FLAG_DISALLOW_INTERCEPT,这个标志位关系到 ViewGroup 的 onInterceptTouch 是否有效。
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }
            

            // 检查是否拦截
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                    // 处理条件为:
                    // 1. 事件为 ACTION_DOWN
                    // 2. 有下级的 View 处理事件
                    
                    // 判断拦截是否失效?mGroupFlags = FLAG_DISALLOW_INTERCEPT 时,onInterceptTouchEvent 是不会被调用的
                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 {
            
                // 非 ACTION_DOWN 事件且没有其他的下级处理该事件的时候,不会再调用 onInterceptTouchEvent
                intercepted = true;
            }

            // 正常事件分发
            // 如果 ViewGroup 决定拦截或者已经有 子 view 处理事件,那么就开始正常的事件分发流程
            if (intercepted || mFirstTouchTarget != null) {
                ev.setTargetAccessibilityFocus(false);
            }


            // 不拦截事件
            if (!canceled && !intercepted) {
                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                        ? findChildWithAccessibilityFocus() : null;

                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--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);


                            // 找到接收该事件的子 View 上,如果找到,则直接跳出循环
                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            // 判断事件是否落在子 view 上,如果是,则跳出循环
                            resetCancelNextUpFlag(child);
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                                    // childIndex points into presorted list, find original index
                                    for (int j = 0; j < childrenCount; j++) {
                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }

                            // The accessibility focus didn't handle the event, so clear
                            // the flag and do a normal dispatch to all children.
                            ev.setTargetAccessibilityFocus(false);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }

                }
            }



从以上的代码中可以看出,当事件传递到 ViewGroup 中的 dispatchTouchEvent 的时候,这次经历了以下的几个重要步骤:

step 1: 重置标志位 mGroupFlags,将 mGroupFlags 标志位设置为非 FLAG_DISALLOW_INTERCEPT,如果 mGroupFlags = FLAG_DISALLOW_INTERCEPT,那么 ViewGroup 将不再调用 onInterceptTouch(),默认 ViewGroup 不拦截任何事件。

step 2: 通过判断事件是否为 ACTION_DOWN 或 mFirstTouchTarget != null 来决定是否向下分发 ACTION_DOWN 之外的事件;
若是子 View 消费 ACTION_DOWN,那么 mFirstTouchTarget 会被赋值,mFirstTouchTarget != null 不成立
若是子 View 不消费 ACTION_DOWN,那么 mFirstTouchTarget 则为 null,ViewGroup 默认拦截 ACTION_DOWN 之后的所有事件,不向下传递。

step 3:通过 mGroupFlags 标志位判断拦截是否有效,若是 mGroupFlags = FLAG_DISALLOW_INTERCEPT,则 ViewGroup 默认不拦截任何事件。

step 4: 循环所有的子 view
(1)判断是否有子 view 已经处理改事件了,如果有则跳出循环,直接向下级子 view 分发事件。
(2)判断点击是否落在某个子 view;

step 5: 如果子 view 消费了事件,那么将 mFirstTouchTarget 进行赋值,mFirstTouchTarget(链表)。

View
View 对于事件的处理要稍微简单一点,注意这里的 View 并不包含 ViewGroup。我们先看看 dispatchTouchEvent 方法。

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 对点击事件的处理过程,首先会判断有没有设置 onTouchListener,如果 onTouchListener 中的 onTouch 返回了 true,那么 onTouchEvent 就不会再被调用,可见 onTouchListener 的优先级要高于 onTouchEvent,这样的处理是方便点击事件在外界进行处理。

View 中的 onTouchEvent 的实现

    public boolean onTouchEvent(MotionEvent event) {

        
        // 不可用状态下,View 依然会消耗点击事件
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
            
            return clickable;
        }
        
        // 如果设置了代理,那么就设置代理的方法
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
    
    
        // 
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    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();

                            if (!focusTaken) {
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }

                        ....
                    }
                    break;


            }

            return true;
        }

        return false;
    }

从上面的代码中可以看出,只要 View 的 CLICKABLE 和 LONG_CLICK 有一个为 true,那么它就会消耗这个事件,即 onTouchEvent 方法返回 true,不管它是不是 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;
    }

到这里,点击事件的源码分析就结束了。

你可能感兴趣的:(事件分发)