View的事件分发机制

总述

事件分发的过程由三个很重要的方法完成

  • //方法事件
    public boolean dispatchTouchEvent(MotionEvent ev)

  • //是否拦截某个事件
    public boolean onInterceptTouchEvent(MotionEvent ev)

  • //处理点击事件,return true ,说明消耗这次事件
    public boolean onTouchEvent(MotionEvent event)/

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

说明

对于一个跟ViewGroup来说,点击事件产生首先传递给 dispatchTouchEvent 方法,对 ViewGroup 的 onInterceptTouchEvent 返回值分情况分析:
a. 返回值为true,说明ViewGroup想要拦截这个事件,从而交个 ViewGroup 的 onTouchEvent 处理。
b. 返回值为false,表示 ViewGroup 不拦截当前事件,这是当前事件传递到它的子元素,接着子元素的 dispatchTouchEvent 方法就会被调用,如此反复知道事件被最终处理。子元素的 dispatchTouchEvent 返回为true时就代表事件被消耗了。

Activity 事件分发

  1. dispatchTouchEvent
  2. onTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();        //空
    }
    if (getWindow().superDispatchTouchEvent(ev)) {  //Window ,实现为 PhoneWindow
        return true;
    }
    return onTouchEvent(ev);
}

PhoneWindow 是Window的唯一实现类,在PhoneWindow的superDispatchTouchEvent方法中

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

到这里逻辑就很清晰了,PhoneWindow 将事件直接传递给 DecorView。通过((ViewGroup)getWindow.getDecorView().findViewById(android.R.id.content)).getChildAt(0),就可以获取 Activity 所设置的 View。

ViewGroup 事件分发

1. dispatchTouchEvent
2. onInterceptTouchEvent
3. onTouchEvent
/**
 * 常说事件传递中的流程是:dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent
 * 在这个链条中dispatchTouchEvent()是处在链首的位置当然也是最重要的.
 * 在dispatchTouchEvent()决定了Touch事件是由自己的onTouchEvent()处理
 * 还是分发给子View处理让子View调用其自身的dispatchTouchEvent()处理.
 *
 *
 * 其实dispatchTouchEvent()和onInterceptTouchEvent()以及onTouchEvent()的关系
 * 在dispatchTouchEvent()方法的源码中体现得很明显.
 * 比如dispatchTouchEvent()会调用onInterceptTouchEvent()来判断是否要拦截.
 * 比如dispatchTouchEvent()会调用dispatchTransformedTouchEvent()方法且在该方法中递归调用
 * dispatchTouchEvent();从而会在dispatchTouchEvent()里最终调用到onTouchEvent()
 *
 *
 *
 * 重点关注:
 * 1 子View对于ACTION_DOWN的处理十分重要!!!!!
 *   ACTION_DOWN是一系列Touch事件的开端,如果子View对于该ACTION_DOWN事件在onTouchEvent()中返回了false即未消费.
 *   那么ViewGroup就不会把后续的ACTION_MOVE和ACTION_UP派发给该子View.在这种情况下ViewGroup就和普通的View一样了,
 *   调用该ViewGroup自己的dispatchTouchEvent()从而调用自己的onTouchEvent();即不会将事件分发给子View.
 *   详细代码请参见如下代码分析.
 *
 * 2 为什么子view对于Touch事件处理返回true那么其上层的ViewGroup就无法处理Touch事件了?????
 *   这个想必大家都知道了,因为该Touch事件被子View消费了其上层的ViewGroup就无法处理该Touch事件了.
 *   那么在源码中的依据是什么呢??请看下面的源码分析
 *
 */
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
    }

    boolean handled = false;
    //为了安全机制,检查当前事件是否应该过滤掉,该方法如果返回true,代表当前事件应该继续分发,如果返回false,代表当前事件应该被drop掉
    //如果返回false,dispatchTouchEvent方法直接返回false了。
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        /**
         * 第一步:对于ACTION_DOWN进行处理(Handle an initial down)
         * 因为ACTION_DOWN是一系列事件的开端,当是ACTION_DOWN时进行一些初始化操作.
         * 从源码的注释也可以看出来:清除以往的Touch状态(state)开始新的手势(gesture)
         * cancelAndClearTouchTargets(ev)中有一个非常重要的操作:
         * 将mFirstTouchTarget设置为null!!!!
         * 随后在resetTouchState()中重置Touch状态标识
         */
        // 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();
        }

        /**
         * 第二步:检查是否要拦截(Check for interception)
         * 在dispatchTouchEvent(MotionEventev)这段代码中
         * 使用变量intercepted来标记ViewGroup是否拦截Touch事件的传递.
         * 该变量在后续代码中起着很重要的作用.
         */
        // Check for interception.
        final boolean intercepted;
        // 事件为ACTION_DOWN或者mFirstTouchTarget不为null(即已经找到能够接收touch事件的目标组件)时if成立
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            //判断disallowIntercept(禁止拦截)标志位
            //因为在其他地方可能调用了requestDisallowInterceptTouchEvent(boolean disallowIntercept)
            //如果是 ACTION_DOWN 就会重置 FLAG_DISALLOW_INTERCEPT 这个标记位,将导致梓View中设置的这个标记位无效。
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            //当没有禁止拦截判断时(即disallowIntercept为false)调用onInterceptTouchEvent(ev)方法
            if (!disallowIntercept) {
                //既然disallowIntercept为false那么就调用onInterceptTouchEvent()方法将结果赋值给intercepted
                //常说事件传递中的流程是:dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent
                //其实在这就是一个体现,在dispatchTouchEvent()中调用了onInterceptTouchEvent()
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {
                //当禁止拦截判断时(即disallowIntercept为true)设置intercepted = false
                intercepted = false;
            }
        } else {
            //当事件不是ACTION_DOWN并且mFirstTouchTarget为null(即没有Touch的目标组件)时
            //设置 intercepted = true表示ViewGroup执行Touch事件拦截的操作。
            // There are no touch targets and this action is not an initial down
            // so this view group continues to intercept touches.
            intercepted = true;
        }

        /**
         * 第三步:检查cancel(Check for cancelation)
         *
         */
        // Check for cancelation.
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;

        /**
         * 第四步:事件分发(Update list of touch targets for pointer down, if needed)
         */
        // Update list of touch targets for pointer down, if needed.
        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        //不是ACTION_CANCEL并且ViewGroup的拦截标志位intercepted为false(不拦截)
        if (!canceled && !intercepted) {
            //处理ACTION_DOWN事件.这个环节比较繁琐.
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                final int actionIndex = ev.getActionIndex(); // always 0 for down
                final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                        : TouchTarget.ALL_POINTER_IDS;

                // Clean up earlier touch targets for this pointer id in case they
                // have become out of sync.
                removePointersFromTouchTargets(idBitsToAssign);

                final int childrenCount = mChildrenCount;
                if (childrenCount != 0) {
                    // 依据Touch坐标寻找子View来接收Touch事件
                    // Find a child that can receive the event.
                    // Scan children from front to back.
                    final View[] children = mChildren;
                    final float x = ev.getX(actionIndex);
                    final float y = ev.getY(actionIndex);

                    // 遍历子View判断哪个子View接受Touch事件
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final View child = children[i];
                        if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            //如果子View不可以接受到本次事件或者本次DOWN事件的坐标不在这个子View的范围内,跳过本次循环。
                            continue;
                        }

                        newTouchTarget = getTouchTarget(child);
                        if (newTouchTarget != null) {
                            // 找到接收Touch事件的子View!!!!!!!即为newTouchTarget.
                            // 既然已经找到了,所以执行break跳出for循环
                            // Child is already receiving touch within its bounds.
                            // Give it the new pointer in addition to the ones it is handling.
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }

                        resetCancelNextUpFlag(child);

                        /**
                         * 如果上面的if不满足,当然也不会执行break语句.
                         * 于是代码会执行到这里来.
                         *
                         * 调用方法dispatchTransformedTouchEvent()将Touch事件传递给子View做
                         * 递归处理(也就是遍历该子View的View树)
                         * 该方法很重要,看一下源码中关于该方法的描述:
                         * Transforms a motion event into the coordinate space of a particular child view,
                         * filters out irrelevant pointer ids, and overrides its action if necessary.
                         * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
                         * 将Touch事件传递给特定的子View.
                         * 该方法十分重要!!!!在该方法中为一个递归调用,会递归调用dispatchTouchEvent()方法!
                         * 在dispatchTouchEvent()中:
                         * 如果子View为ViewGroup并且Touch没有被拦截那么递归调用dispatchTouchEvent()
                         * 如果子View为View那么就会调用其onTouchEvent(),这个就不再赘述了.
                         *
                         *
                         * 该方法返回true则表示子View消费掉该事件,同时进入该if判断.
                         * 满足if语句后重要的操作有:
                         * 1 给newTouchTarget赋值
                         * 2 给alreadyDispatchedToNewTouchTarget赋值为true.
                         *   看这个比较长的英语名字也可知其含义:已经将Touch派发给新的TouchTarget
                         * 3 执行break.
                         *   因为该for循环遍历子View判断哪个子View接受Touch事件,既然已经找到了
                         *   那么就跳出该for循环.
                         * 4 注意:
                         *   如果dispatchTransformedTouchEvent()返回false即子View
                         *   的onTouchEvent返回false(即Touch事件未被消费)那么就不满足该if条件,也就无法执行addTouchTarget()
                         *   从而导致mFirstTouchTarget为null.那么该子View就无法继续处理ACTION_MOVE事件
                         *   和ACTION_UP事件!!!!!!!!!!!!!!!!!!!!!!
                         * 5 注意:
                         *   如果dispatchTransformedTouchEvent()返回true即子View
                         *   的onTouchEvent返回true(即Touch事件被消费)那么就满足该if条件.
                         *   从而mFirstTouchTarget不为null!!!!!!!!!!!!!!!!!!!
                         * 6 小结:
                         *   对于此处ACTION_DOWN的处理具体体现在dispatchTransformedTouchEvent()
                         *   该方法返回boolean,如下:
                         *   true---->事件被消费----->mFirstTouchTarget!=null
                         *   false--->事件未被消费---->mFirstTouchTarget==null
                         *   因为在dispatchTransformedTouchEvent()会调用递归调用dispatchTouchEvent()和onTouchEvent()
                         *   所以dispatchTransformedTouchEvent()的返回值实际上是由onTouchEvent()决定的.
                         *   简单地说onTouchEvent()是否消费了Touch事件(true or false)的返回值决定了dispatchTransformedTouchEvent()
                         *   的返回值!!!!!!!!!!!!!从而决定了mFirstTouchTarget是否为null!!!!!!!!!!!!!!!!从而进一步决定了ViewGroup是否
                         *   处理Touch事件.这一点在下面的代码中很有体现.
                         */
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            // Child wants to receive touch within its bounds.
                            mLastTouchDownTime = ev.getDownTime();
                            mLastTouchDownIndex = i;
                            mLastTouchDownX = ev.getX();
                            mLastTouchDownY = ev.getY();
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }
                    }
                }

                /**
                 * 该if条件表示:
                 * 经过前面的for循环没有找到子View接收Touch事件并且之前的mFirstTouchTarget不为空
                 */
                if (newTouchTarget == null && mFirstTouchTarget != null) {
                    // Did not find a child to receive the event.
                    // Assign the pointer to the least recently added target.
                    newTouchTarget = mFirstTouchTarget;
                    while (newTouchTarget.next != null) {
                        newTouchTarget = newTouchTarget.next;
                    }
                    //newTouchTarget指向了最初的TouchTarget
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                }
            }
        }

        /**
         * 分发Touch事件至target(Dispatch to touch targets)
         *
         * 经过上面对于ACTION_DOWN的处理后mFirstTouchTarget有两种情况:
         * 1 mFirstTouchTarget为null
         * 2 mFirstTouchTarget不为null
         *
         * 当然如果不是ACTION_DOWN就不会经过上面较繁琐的流程
         * 而是从此处开始执行,比如ACTION_MOVE和ACTION_UP
         */
        // Dispatch to touch targets.
        if (mFirstTouchTarget == null) {
            /**
             * 情况1:mFirstTouchTarget为null
             *
             * 经过上面的分析mFirstTouchTarget为null就是说Touch事件未被消费.
             * 即没有找到能够消费touch事件的子组件或Touch事件被拦截了,
             * 则调用ViewGroup的dispatchTransformedTouchEvent()方法处理Touch事件则和普通View一样.
             * 即子View没有消费Touch事件,那么子View的上层ViewGroup才会调用其onTouchEvent()处理Touch事件.
             * 在源码中的注释为:No touch targets so treat this as an ordinary view.
             * 也就是说此时ViewGroup像一个普通的View那样调用dispatchTouchEvent(),且在dispatchTouchEvent()
             * 中会去调用onTouchEvent()方法.
             * 具体的说就是在调用dispatchTransformedTouchEvent()时第三个参数为null.
             * 第三个参数View child为null会做什么样的处理呢?
             * 请参见下面dispatchTransformedTouchEvent()的源码分析
             *
             * 这就是为什么子view对于Touch事件处理返回true那么其上层的ViewGroup就无法处理Touch事件了!!!!!!!!!!
             * 这就是为什么子view对于Touch事件处理返回false那么其上层的ViewGroup才可以处理Touch事件!!!!!!!!!!
             */
            // No touch targets so treat this as an ordinary view.
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
            /**
             * 情况2:mFirstTouchTarget不为null即找到了可以消费Touch事件的子View且后续Touch事件可以传递到该子View
             * 在源码中的注释为:
             * Dispatch to touch targets, excluding the new touch target if we already dispatched to it.
             * Cancel touch targets if necessary.
             */
            // Dispatch to touch targets, excluding the new touch target if we already
            // dispatched to it.  Cancel touch targets if necessary.
            TouchTarget predecessor = null;
            TouchTarget target = mFirstTouchTarget;
            while (target != null) {
                final TouchTarget next = target.next;
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    //对于非ACTION_DOWN事件继续传递给目标子组件进行处理,依然是递归调用dispatchTransformedTouchEvent()
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                    if (cancelChild) {
                        if (predecessor == null) {
                            mFirstTouchTarget = next;
                        } else {
                            predecessor.next = next;
                        }
                        target.recycle();
                        target = next;
                        continue;
                    }
                }
                predecessor = target;
                target = next;
            }
        }

        /**
         * 处理ACTION_UP和ACTION_CANCEL
         * Update list of touch targets for pointer up or cancel, if needed.
         * 在此主要的操作是还原状态
         */
        // Update list of touch targets for pointer up or cancel, if needed.
        if (canceled
                || actionMasked == MotionEvent.ACTION_UP
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            resetTouchState();
        } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
            final int actionIndex = ev.getActionIndex();
            final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
            removePointersFromTouchTargets(idBitsToRemove);
        }
    }

    if (!handled && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
    }
    return handled;
}

a. 对于ACTION_DOWN进行处理:
cancelAndClearTouchTargets(ev);
resetTouchState(); //重置标记位
b. 检查是否要拦截(Check for interception)
mFirstTouchTarget,FLAG_DISALLOW_INTERCEPT标记位启动了关键作用。
c. 分发Touch事件至target
经过上面对于ACTION_DOWN的处理后mFirstTouchTarget有两种情况:
1 mFirstTouchTarget为null

//No touch targets so treat this as an ordinary view
handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);

2 mFirstTouchTarget不为null
找到了可以消费Touch事件的子View

dispatchTouchEvent 的返回值说明
true 表示消耗了此事件

View 对点击事件的处理

  1. dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
    boolean result = false;
    ...
    
    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;
        }
    }

    ...

    return result;
}

首先会判断有没有设置 OnTouchListener,如果 OnTouchListener 中的 onTouch 方法返回 true,那么 onTouchEvent 就不会调用,也就是说 ** OnTouchListener的优先级高于 onTouchEvent,这样做的好处是方便在外界处理点击事件。(dispatchTouchEvent返回true,说明分发完成,事件不会再去传递。**)

事件传递机制的若干总结

研究demo戳我git:Jack_TouchEvent_demo

  1. 事件序列:从手指接触屏幕那一刻开始,到手指离开屏幕有的那一刻结束。以 down 事件开始,中间含有不定数量 move 事件,最终以 up 事件结束。
  • 一旦一个元素拦截到某个事件,那么同一个事件序列内的所有事件都会直接交由它处理.(同一个事件序列中的事件不能分别由两个 view 同时处理)
  • 一旦一个元素拦截到某个事件,那么同一个事件序列内的所有事件都会直接交由它处理.并且它的 onInterceptTouchEvent 不会再调用。
  • 某个 View 一旦开始处理事件,如果不消耗 ACTION_DOWN 事件(onTouchEvent 返回 false),那么同一事件序列的其他事件都不会交给它来处理。
  • ViewGroup 默认不拦截任何事件,它的 onInterceptTouchEvent 返回 false。
  • View 没有 onInterceptTouchEvent 方法,一旦有事件传递给它,那么它的 onTouchEvent 就会调用。
  • 事件传递的过程是有外向内的,即事件总是先传递给父元素,然后再由父元素分发给子 View,通过 requestDisallowInterceptTouchEvent 方法可以在子元素中干预父元素的事件分发过程,但是 ACTION_DOWN 事件除外。
  • 事件的传递顺序 Activity -> Window -> View(UI界面架构:Activity -> PhoneWindow -> DecorView(TitleView ContentView(FrameLayout));)
View的事件分发机制_第1张图片

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