Android 事件分发的源码分析

前提条件源码是基于android 26分析的

基础知识

什么是触摸事件

触摸事件,是 Android 用来描述你的手对屏幕做的事情的最小单元。关键词有两个:手势(你的手对屏幕做的事情)、最小单元。
手势:按下、移动、抬起、点击事件、长按事件、滑动事件。
最小单元:不可在拆分,比如按下(down)、移动(move)、抬起(up)就是不可再拆分的。而点击事件、长按事件、滑动事件则是由那些不可再拆分的事件组合起来的。比如点击事件是由按下、抬起组成的。长按事件是按下并保持一段时间不动。注意一下滑动事件和移动(move)的区别,滑动事件可以理解为是由若干个“move”组合起来的,系统对触摸的捕捉是很灵敏的,手指放在屏幕上稍微抖一下,都会产生“move”事件。
这里还有一个取消(cancel)事件,暂时不予理会

MotionEvent 对象

MotionEvent 对象就是对触摸事件相关信息的封装
事件类型(包括但不局限于):ACTION_DOWN(按下)、ACTION_ MOVE(移动)、ACTION_ UP(抬起)。

坐标系

坐标还分参考系,有以屏幕作为参考系的坐标值,也有以被触摸的 View 作为参考系的坐标值。
MotionEvent 的对象内的方法与坐标系是存在一定关联的具体的可参考下面的图


View坐标系
一个完整的事件序列

触摸事件与一个完整的事件序列的区别

  • 触摸事件
    最小单元,任何一个对屏幕的操作都是由多个触摸事件构成的,触摸事件分别有按下、移动、抬起、取消等
  • 一个完整的事件序列
    ACTION_DOWN(一个)–>ACTION_MOVE(数量不定)–>ACTION_UP(一个),从 ACTION_DOWN 到 ACTION_UP,称为一个完整的事件序列,即某一次手指按下到手指抬起算是一个完整的事件序列,下一次手指按下到抬起是另一个完整的事件序列。
    笔者没有真的考究过 Android 官方是不是真的有“事件序列”这么一个概念性的名词,只是为了分析源码方便理解才这样说的,这也是android“拟人化”行为的一种标识吧,因为从ViewGroup 的源码里面,确实是可以找到在遇到 ACTION_DOWN 时清除某些标记位(以避免受到前一个事件序列的影响)

为什么要事件分发

简单地讲,事件分发就是为了解决“谁来干活”的问题。当一个事件发生,有超过一个View(或者 ViewGroup)能够对它进行响应(处理)时,就要确定到底谁来处理这个事件。

View的事件分发

View的事件分发,是指 View.java 源码里对触摸事件进行传递的流程

流程图

View 事件分发的流程图是一个主干流程,去掉一些细节的流程

流程图如下


View的事件分发

源码

public boolean dispatchTouchEvent(MotionEvent event) {
        ....//省略无关的代码

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

可以看到关键代码那里的条件就是流程图上面的条件
我们再来看onTouchEvent的源码,这里就是平常设置点击事件的回调地方

        public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();

        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

        ....//省略无关代码
        //如果view的clickable属性设置为false也不会响应点击事件
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    if ((viewFlags & TOOLTIP) == TOOLTIP) {
                        handleTooltipUp();
                    }
                    if (!clickable) {
                        removeTapCallback();
                        removeLongPressCallback();
                        mInContextButtonPress = false;
                        mHasPerformedLongPress = false;
                        mIgnoreNextUpEvent = false;
                        break;
                    }
                    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();

                            // 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)) {
                                    performClick();
                                }
                            }
                        }
                        ....//省略无关代码
                    }
                    mIgnoreNextUpEvent = false;
                    break;

            return true;//返回这个的话就说明事件已经消耗了
        }

        return false;//返回这个的话就说明事件抛回给它的父类处理
    }

流程说明

有可能处理事件的有两个地方,一个是外部设置的 OnTouchListener 监听器,即OnTouchListener的onTouch(),一个是 View 内部的处理方法,即 onTouchEvent()。而且外部设置的监听器优先获取事件。

当外部设置的监听器处理了事件(即有设置监听器、View 处于 ENABLE 状态、监听器在onTouch()里面返回 true 三者均成立),dispatchTouchEvent() 返回 true,当前 View的 onTouchEvent() 不会触发。

如果外部的监听器不处理事件,则dispatchTouchEvent() 的返回值由 View 自己的onTouchEvent()决定。

注意一下,对于上级 ViewGroup(也就是当前 View 的父容器)而言,它只认识子 View的dispatchTouchEvent()方法,不认识另外两个处理事件的方法。子View的OnTouchListener 和 onTouchEvent() 都是在自己的 dispatchTouchEvent() 里面调用的,他们两个会影响 dispatchTouchEvent() 的返回值,但是对于上级 ViewGroup 而言,它只认识 dispatchTouchEvent() 的返回值就足够了。

ViewGroup 的事件分发

流程图

流程如下图所示:


ViewGroup 的事件分发

源码

public boolean dispatchTouchEvent(MotionEvent ev) {
         ....//省略无关代码
        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {

             /**
             * 第一步:对于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.
            // 使用变量intercepted来标记ViewGroup是否拦截Touch事件的传递.
            final boolean intercepted;
            //ACTION_DOWN事件或者已经找到了事件分发的目标对象,在一个事件序列是非ACTION_DOWN事件都会进入这里
            //对应流程图中的第一个判断分支
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                //设置禁止拦截的标记 1.ACTION_DOWN事件与非ACTION_DOWN事件都会走这里
                //因为在其他地方可能调用了requestDisallowInterceptTouchEvent(boolean disallowIntercept)来影响这个变量的值,
                //所以在一个事件序列中如果多个事件之后确定了是由谁来处理事件,就在这个事件序列中满足一定条件可以设置这个值
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    //再次询问是否会拦截 1.ACTION_DOWN事件与非ACTION_DOWN事件都会走这里
                    //常说事件传递中的流程是:dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent
                    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;
            }
            ....//省略无关代码
            //不是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 childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        // Find a child that can receive the event.
                        // Scan children from front to back.
                        final ArrayList preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        //遍历viewGroup下所有的子view找到可以消费事件的子view,并且把事件消费的目标赋值
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);

                            ....//省略无关代码
                            //这个条件是第二次发生点击事件(第二个事件序列),这个快速找到事件消费的目标
                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                //找到接收事件的目标所以直接跳出
                                // 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;
                            }
                            /**
                             * 如果上面的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();
                                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();
                                //这里是对第一次事件消费的目标赋值(mFirstTouchTarget的赋值的地方)
                                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();
                    }

                    //该条件表示没有找到子view接收事件并且之前的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
             * 
             * 经过上面对于ACTION_DOWN的处理后mFirstTouchTarget有两种情况:
             * 1 mFirstTouchTarget为null
             * 2 mFirstTouchTarget不为null
             * 
             * 当然如果不是ACTION_DOWN就不会经过上面较繁琐的流程
             * 而是从此处开始执行,比如ACTION_MOVE和ACTION_UP
            */
            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                //简要分析
                //1.子view不处理事件会执行这里
                //2.无法找到事件消费的目标
                //都是走super.dispatchTouchEvent(event);
                /**
                 * 详细分析
                 * 情况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()的源码分析
                 * 
                 * 
                */
                // 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,
                 * 但是在事件还没确定是viewGroup还是view处理时会一直流经两个的事件流中
                */
                // 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;
                        }
                        //对于非ACTION_DOWN事件如果中途拦截了(intercepted = true),这个条件会把mFirstTouchTarget置为null
                        //等下一个touch事件就会把事件给viewGroup自己处理
                        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;
    }

你可能感兴趣的:(Android 事件分发的源码分析)