美文网首页
Android 事件分发的源码分析

Android 事件分发的源码分析

作者: 梦幻世界wjl | 来源:发表于2018-12-28 19:41 被阅读0次

    前提条件源码是基于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<View> 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 事件分发的源码分析

          本文链接:https://www.haomeiwen.com/subject/grjelqtx.html