Android ViewGroup事件分发dipatchTouchEvent源码注释分析

来源:互联网 发布:淘宝授权书可以造假吗 编辑:程序博客网 时间:2024/06/04 18:09

一直以来都自学Android,看各种大神说提高技能的途径之一就是写博客,一来可以当作笔记,而来可以获得别的大牛的提示和指导,所以现在开始自己的第一篇Android博客,只是将ViewGroup的dipatchEvent源码按照自己的理解加了些注释,这里的没FirstTouchTarget是一个链表,我猜想它应该是记录的事件传递下去直到最终处理事件的控件的所有层级上的控件,如果有不对的地方还请海涵,能指正就更好了吐舌头

package source;


public boolean dispatchTouchEvent(MotionEvent ev) {

    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
    }


    // If the event targets the accessibility focused view and this is it, start
    // normal event dispatch. Maybe a descendant is what will handle the click.
    if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
        ev.setTargetAccessibilityFocus(false);
    }


    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)方法中将mFirstTouchTarget设置为了null
             * ,接着在resetTouchState()方法中重置Touch状态标识。
             */

            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }


        // Check for interception.
        final boolean intercepted;
        /**
         * 1、如果是ACTION_DOWN会进入方法体进行要不要拦截的判断
         * 2、如果已经找到子View处理前面的事件(mFirstTouchTarget不为空)
         * ,也进入方法体进行判断 
         */

        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
        /**
        * 子View控件通过getParent().requestDisallowIntercept(boolean)
        * 设置的不允许父控件拦截的标记
        * 例外:由于上方在ACTION_DOWN时会重置该标记,所以在ACTION_DOWN事件,
        * 该标记一定是false,也就是一定会执行还是通过onInterceptTouchEvent(ev)方法。
        */

            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
            /**
            * 如果允许拦截,还是通过onInterceptTouchEvent(ev)判断是否要拦截
            */
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {
            /**
            * 如果不允许拦截,则直接判定拦截标记为false
            * 代表事件会传递给子控件
            */

                intercepted = false;
            }
        } else {
           /**
            * 非ACTION_DOWN事件+前面的事件序列未找到处理该事件的子VIEW
            * 则本控件拦截
            */

            intercepted = true;
        }


        // If intercepted, start normal event dispatch. Also if there is already
        // a view that is handling the gesture, do normal event dispatch.
        if (intercepted || mFirstTouchTarget != null) {
            ev.setTargetAccessibilityFocus(false);
        }


        // 通过标记和action检查cancel,然后将结果赋值给局部boolean变量canceled。
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;


        // Update list of touch targets for pointer down, if needed.
        //split来标记,默认是true,作用是是否把事件分发给多个子View
        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        /**
         * 事件不是ACTION_CANCEL且ViewGroup的拦截标志位intercepted为false(不拦截)则会进入其中。
         */

        if (!canceled && !intercepted) {


            // If the event is targeting accessiiblity focus we give it to the
            // view that has accessibility focus and if it does not handle it
            // we clear the flag and dispatch the event to all children as usual.
            // We are looking up the accessibility focused host to avoid keeping
            // state since these events are very rare.
            View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                    ? findChildWithAccessibilityFocus() : null;


            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;
                /**
                 * 判断子View个数是否大于0
                 */

                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.
                    /**
                     * 获取子View的List集合
                     */

                    final ArrayList<View> preorderedList = buildOrderedChildList();
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;
                    /**
                * for循环i从childrenCount - 1开始遍历到0,倒序遍历所有的子view,
                * 这是因为preorderedList中的顺序是按照addView或者XML布局文件中的顺序来的,
                * 后addView添加的子View,会因为Android的UI后刷新机制显示在上层;
                * 假如点击的地方有两个子View都包含的点击的坐标,
                * 那么后被添加到布局中的那个子view会先响应事件;
                * 这样其实也是符合人的思维方式的,
                * 因为后被添加的子view会浮在上层,
                * 所以我们去点击的时候一般都会希望点击最上层的那个组件先去响应事件。
                */

                    for (int i = childrenCount - 1; i >= 0; i--) {
                   
                        final int childIndex = customOrder
                                ? getChildDrawingOrder(childrenCount, i) : i;
                        final View child = (preorderedList == null)
                                ? children[childIndex] : preorderedList.get(childIndex);


                        // If there is a view that has accessibility focus we want it
                        // to get the event first and if not handled we will perform a
                        // normal dispatch. We may do a double iteration but this is
                        // safer given the timeframe.
                        if (childWithAccessibilityFocus != null) {
                            if (childWithAccessibilityFocus != child) {
                                continue;
                            }
                            childWithAccessibilityFocus = null;
                            i = childrenCount - 1;
                        }


                        if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }
                        /**
                         * getTouchTarget去查找当前子View是否在mFirstTouchTarget.next
                         * 这条target链中的某一个targe中,
                         * 如果在则返回这个target,否则返回null
                         */

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


                        resetCancelNextUpFlag(child);
                        /**
                         * 调用方法dispatchTransformedTouchEvent()将Touch事件传递给特定的子View
                         * 在该方法中为一个递归调用,会递归调用dispatchTouchEvent()方法。
                         * 在dispatchTouchEvent()中如果子View为ViewGroup并且Touch事件没有被拦截
                         * 那么递归调用dispatchTouchEvent(),如果子View为View那么就会调用其onTouchEvent()。
                         * dispatchTransformedTouchEvent方法如果返回true则表示子View消费掉该事件,同时进入该if判断
                         * dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)内主要代码:
                         * if(child==null){
                         *    //交给View的dispatchTouchEvent(event),即自己处理该事件
                         *    handled = super.dispatchTouchEvent(event)
                         * }else{
                         *    //交给子View去处理
                         *    handled = child.dispatchTouchEvent(event)
                         * }
                         */

                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            // Child wants to receive touch within its bounds.
                        /**
                        * 代表找到了子View处理该事件
                        */

                            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赋值;
                             * 给alreadyDispatchedToNewTouchTarget赋值为true;
                             * 执行break,因为该for循环遍历子View判断哪个子View接受Touch事件,
                             * 既然已经找到了就跳出该外层for循环;
                             * addTouchTarget(child, idBitsToAssign)中对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);
                    } //遍历子View结束
                    if (preorderedList != null) preorderedList.clear();
                }


                if (newTouchTarget == null && mFirstTouchTarget != null) {
                    /**
                     * 该if表示经过前面的for循环没有找到子View接收Touch事件
                     * 并且之前的mFirstTouchTarget不为空则为真
                     * 然后newTouchTarget指向了最初的TouchTarget
                     */

                    newTouchTarget = mFirstTouchTarget;
                    while (newTouchTarget.next != null) {
                        newTouchTarget = newTouchTarget.next;
                    }
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                }
            } //if (actionMasked == MotionEvent.ACTION_DOWN
          //  || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
        //    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) 结束

            
        }  //(!canceled && !intercepted) 结束


        // Dispatch to touch targets.
        /**
         * 非ACTION_DOWN事件不会经过以上的代码
         * 所有事件都会经过以下代码
         */

        if (mFirstTouchTarget == null) {
            // No touch targets so treat this as an ordinary view.
        /**
        * if判断的mFirstTouchTarget为null时,也就是说Touch事件未被消费,
        * 2种情况:1、没有子View2、子View在onTouchEvent方法中返回了false
        * 则调用ViewGroup的dispatchTransformedTouchEvent()方法处理Touch事件
        * 调用dispatchTransformedTouchEvent()时第三个参数为null
        * if(child==null){
             *    //交给View的dispatchTouchEvent(event),即自己处理该事件
             *    handled = super.dispatchTouchEvent(event)
             * }else{
             *    //交给子View去处理
             *    handled = child.dispatchTouchEvent(event)
             * }
        */

            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
            // 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) {
                /**
                * 表明ACTION_DOWN已经被子VIew处理
                * 后续事件newTouchTarget永远是null,不会执行到这里
                */

                    handled = true;
                } else {
                    /**
                     *   cancelChild 这个标记表明只要intercepted为true,该标记则为true。

                     * 在下方有个if判断,如果该标记为true,分两种情况:1、在onIntercepTouchEvent中拦截了非Action_Down事件

                     * 2、在手指划出处理前序事件的控件。这两种情况都会使得后续的事件在传递到处理Action_down的控件时候转化为ACTION_CANCEL事件

                     * 原因在于最后分析的dispatchTransformedTouchEvent源码,这里有个印象
                     */
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    /**
                     * 处理ACTION_DOWN后续事件:
                     * 若mFirstTarget不为空,则还是交给子View直接处理
                     * 注意:1、这里的没FirstTaget是个链表,不是最终处理事件的View,
                     * 这里的while循环表明会一直查到target链表的最终处理的View  

                     *注意第二个参数,若cancelChild为true

                     *     dispatchTransformedTouchEvent中有这样一段源码

                     *

  1.  final int oldAction = event.getAction();  
  2.         if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {  
  3.             event.setAction(MotionEvent.ACTION_CANCEL);  
  4.             if (child == null) {  
  5.                 handled = super.dispatchTouchEvent(event);  
  6.             } else {  
  7.                 handled = child.dispatchTouchEvent(event);  
  8.             }  
  9.             event.setAction(oldAction);  
  10.             return handled;  
  11.         }  
                     *这里的cancel即为cancelChild,可以看出它先把原先的事件类型进行保存,然后设置其为ACTION_CANCEL,再传给子控件,最后又把原先的事件类型设置回来。

                     *这就解释了为什么ACTION_MOVE或ACTION_UP被拦截了后,子控件是收到ACTION_CANCEL事件的
                     */

                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                        handled = true;
                    }

                    //此处判定cancelChild标记,如果拦截了(intercepted为true),进入方法体
                    if (cancelChild) {

                        //此处结和上面的while (target != null)可知最终predecessor和 mFirstTouchTarget都会置为null                  

                        if (predecessor == null) {

                           //对应在onIntercepTouchEvent中拦截了此次非Action_Down事件,

                           //此次事件之后的后续事件在一开始判定是否要拦截时候就会直接设置intercepted标记为true

                           //从而不会走onIntercepTouchEvent方法了,这也就解释了从哪个如果从ACTION_MOVE拦截,

                           //子View也不会接受到后续的ACTION_MOVE和ACTION_UP事件
                            mFirstTouchTarget = next;
                        } else {

                           //我猜想这个对应手指划出了子View的情况
                            predecessor.next = next;
                        }
                        target.recycle();
                        target = next;
                        continue;
                    }
                }
                predecessor = target;
                target = next;
            }
        }


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