Android下的事件分发机制(结合源码分析)

先借助于A、B、C三层假想的view进行一定场景下的事件分发机制分析,然后在深入到源码中。

一、事件是由外向内传递的,下面的事件传递流程是A-B-C,A是最外层view,其次是B,最里层的是C,下面从B开始进行分析。

   1、action_down事件到达B时,会调用B的dispatchTouchEvent方法,然后会在dispatchTouchEvent内部调用onInterceptTouchEvent方法判断是否要拦截事件。

   2、如果onInterceptTouchEvent返回true则拦截事件,事件由B处理,不会往下继续传递给C,C不会在接受到任何的事件,而会调用B的onTouchEvent方法,如果onTouchEvent返回true,则down事件结束,被B消费掉了【后续的move和up事件有可能会到达B,也有可能到达不了B,因为要看A是否拦截move和up事件】,如果onTouchEvent返回false,则down事件会向A回传调用A的onTouchEvent方法,后续的move和up事件也不会在到达B了。

   3、如果onInterceptTouchEvent返回false则不拦截事件,事件继续向下传递,传递到C,会调用C的dispatchTouchEvent方法。C是一个view没有onInterceptTouchEvent方法,会直接调用C的onTouchEvent方法,后面的流程就跟B一样了。这样一个down事件就由上层view传递给了下一级子View了。

   4、如果B和C之间还有多层view【容器】嵌套,其分发流程是跟B传到C的流程是一样的,就这样循环下去,直到完成整个事件分发过程。


二、B的onInterceptTouchEvent方法并不是在每一个事件中都会执行的,它的执行是有条件的,下面说明原因:
    1、根据源码:
       // Check for interception.
   final boolean intercepted;
   if (actionMasked == MotionEvent.ACTION_DOWN
           || mFirstTouchTarget != null) {
       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 {
       // There are no touch targets and this action is not an initial down
       // so this view group continues to intercept touches.
        intercepted = true;
        }
    由上面的源码可知,只有当事件类型是down事件或者mFirstTouchTarget != null时才会执行onInterceptTouchEvent方法。

    2、如果C没有消费down事件,那么mFirstTouchTarget就为null。所以如果在down事件到来时,B拦截了该事件,那么mFirstTouchTarget就为nul,那么后续move和up事件到来时,就不会去调用自身的onInterceptTouchEvent方法了,而是直接就处理了move和up事件。

    3、如果C消费了down事件,那么mFirstTouchTarget != null,如果move和up事件到达B时,B还是会调用onInterceptTouchEvent去判断是否需要拦截,如果不拦截,就会继续传递给C去处理。

    4、由上可知,如果C没有消费down事件,那么后续它也不会在接受到任何事件了。如果C消费了down事件,那么后续的move和up事件由可能交给C去处理,也有可能被B拦截掉。这样就有了一种解决BC事件冲突的一种思路了:B不要拦截down事件,C要消费掉down事件,move事件和up事件可以根据具体的业务逻辑去判断B到底要不要拦截【是交给B处理还是交给C处理】。


三、还有一种情况,就是使用requestDisallowInterceptTouchEvent方法,通过requestDisallowInterceptTouchEvent来设置FLAG_DISALLOW_INTERCEPT标记位到达目的,这个方法在子view   中使用以达到能影响上层view的事件分发过程的效果。如果子view中调用该方法并设置值为true,那么上层view将不能拦截除了down事件以外的事件,不能拦截down事件的原因:
    根源源码:
    // 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();
   }
由源码可知,如果是down事件,那么上层view会在resetTouchState方法中重置FLAG_DISALLOW_INTERCEPT,这样就导致设置requestDisallowInterceptTouchEvent是无效的。


四、根据上面的分析可知,上层view一旦拦截了down事件,那么其子view将永远不会在接受到任何事件了。所以如果不是确定需要,一般不要在上层view中拦截down事件,而将down事件交给子view去处    理,这样后续的move事件就可以灵活的根据业务逻辑去分发了(onInterceptTouchEvent和requestDisallowInterceptTouchEvent配合着去处理move事件)。


五、从源码角度分析view的事件分发机制:

          1、view的dispatchTouchEvent和onTouchEvent、以及其他相关的源码
       
   /**
    * Pass the touch screen motion event down to the target view, or this
    * view if it is the target.
    *
    * @param event The motion event to be dispatched.
    * @return True if the event was handled by the view, false otherwise.
    */
   public boolean dispatchTouchEvent(MotionEvent event) {
       // If the event should be handled by accessibility focus first.
       if (event.isTargetAccessibilityFocus()) {
           // We don't have focus or no virtual descendant has it, do not handle the event.
           if (!isAccessibilityFocusedViewOrHost()) {
               return false;
           }
           // We have focus and got the event, then use normal event dispatch.
           event.setTargetAccessibilityFocus(false);
       }

       boolean result = false;

       if (mInputEventConsistencyVerifier != null) {
           mInputEventConsistencyVerifier.onTouchEvent(event, 0);
       }

       final int actionMasked = event.getActionMasked();
       if (actionMasked == MotionEvent.ACTION_DOWN) {
           // Defensive cleanup for new gesture
           stopNestedScroll();
       }

       //关键代码
       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;
           }
       }

       if (!result && mInputEventConsistencyVerifier != null) {
           mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
       }

       // Clean up after nested scrolls if this is the end of a gesture;
       // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
       // of the gesture.
       if (actionMasked == MotionEvent.ACTION_UP ||
               actionMasked == MotionEvent.ACTION_CANCEL ||
               (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
           stopNestedScroll();
       }

       return result;
   }


    /**
    * Implement this method to handle touch screen motion events.
    * <p>
    * If this method is used to detect click actions, it is recommended that
    * the actions be performed by implementing and calling
    * {@link #performClick()}. This will ensure consistent system behavior,
    * including:
    * <ul>
    * <li>obeying click sound preferences
    * <li>dispatching OnClickListener calls
    * <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
    * accessibility features are enabled
    * </ul>
    *
    * @param event The motion event.
    * @return True if the event was handled, false otherwise.
    */
   public boolean onTouchEvent(MotionEvent event) {
       final float x = event.getX();
       final float y = event.getY();
       final int viewFlags = mViewFlags;
       final int action = event.getAction();

       //如果view的Enabled为false,就直接返回,不会继续执行了
       if ((viewFlags & ENABLED_MASK) == DISABLED) {
           if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
               setPressed(false);
           }
           // A disabled view that is clickable still consumes the touch
           // events, it just doesn't respond to them.
           return (((viewFlags & CLICKABLE) == CLICKABLE
                   || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                   || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
       }

       if (mTouchDelegate != null) {
           if (mTouchDelegate.onTouchEvent(event)) {
               return true;
           }
       }

       //如果view的Enabled为true,就继续进行下面操作
       if (((viewFlags & CLICKABLE) == CLICKABLE ||
               (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
               (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
           switch (action) {
               case MotionEvent.ACTION_UP:
                   boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                   if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                       // take focus if we don't have it already and we should in
                       // touch mode.
                       boolean focusTaken = false;
                       if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                           focusTaken = requestFocus();
                       }

                       if (prepressed) {
                           // The button is being released before we actually
                           // showed it as pressed.  Make it show the pressed
                           // state now (before scheduling the click) to ensure
                           // the user sees it.
                           setPressed(true, x, y);
                      }

                      //此段代码会调用view的OnClickListener
                       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();
                               }
                           }
                       }

                       if (mUnsetPressedState == null) {
                           mUnsetPressedState = new UnsetPressedState();
                       }

                       if (prepressed) {
                           postDelayed(mUnsetPressedState,
                                   ViewConfiguration.getPressedStateDuration());
                       } else if (!post(mUnsetPressedState)) {
                           // If the post failed, unpress right now
                           mUnsetPressedState.run();
                       }

                       removeTapCallback();
                   }
                   mIgnoreNextUpEvent = false;
                   break;

               case MotionEvent.ACTION_DOWN:
                   mHasPerformedLongPress = false;

                   if (performButtonActionOnTouchDown(event)) {
                       break;
                   }

                   // Walk up the hierarchy to determine if we're inside a scrolling container.
                   boolean isInScrollingContainer = isInScrollingContainer();

                   // For views inside a scrolling container, delay the pressed feedback for
                   // a short period in case this is a scroll.
                   if (isInScrollingContainer) {
                       mPrivateFlags |= PFLAG_PREPRESSED;
                       if (mPendingCheckForTap == null) {
                           mPendingCheckForTap = new CheckForTap();
                       }
                       mPendingCheckForTap.x = event.getX();
                       mPendingCheckForTap.y = event.getY();
                       postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                   } else {
                       // Not inside a scrolling container, so show the feedback right away
                       setPressed(true, x, y);
                       checkForLongClick(0);
                   }
                   break;

               case MotionEvent.ACTION_CANCEL:
                   setPressed(false);
                   removeTapCallback();
                   removeLongPressCallback();
                   mInContextButtonPress = false;
                   mHasPerformedLongPress = false;
                   mIgnoreNextUpEvent = false;
                   break;

               case MotionEvent.ACTION_MOVE:
                   drawableHotspotChanged(x, y);
                   // Be lenient about moving outside of buttons
                   if (!pointInView(x, y, mTouchSlop)) {
                       // Outside button
                       removeTapCallback();
                       if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                           // Remove any future long press/tap checks
                           removeLongPressCallback();

                           setPressed(false);
                       }
                   }
                   break;
           }

           return true;
       }

       return false;
   }


   /**
    * Call this view's OnClickListener, if it is defined.  Performs all normal
    * actions associated with clicking: reporting accessibility event, playing
    * a sound, etc.
    *
    * @return True there was an assigned OnClickListener that was called, false
    *         otherwise is returned.
    */
   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);
       return result;
   }


    /**
    * Register a callback to be invoked when this view is clicked. If this view is not
    * clickable, it becomes clickable.
    *
    * @param l The callback that will run
    *
    * @see #setClickable(boolean)
    */
   public void setOnClickListener(@Nullable OnClickListener l) {
       if (!isClickable()) {
           setClickable(true);
       }
       getListenerInfo().mOnClickListener = l;
   }


2、结合上面的源码进行view的事件分发机制的分析
  控制view事件分发的方法:dispatchTouchEvent  onTouchEvent
  对view事件分发有影响的设置: Enabled(View是否可用)  Clickable和OnClickListener(View是否可点击)  OnTouchListener(View是否设置了touch监听)根据这些因素进行            分情况分析:
  A、如果view设置了OnTouchListener,并且view的Enabled是true,并且OnTouchListener的onTouch返回值是true,那么就不会在执行view的onTouchEvent方法,
         此时dispatchTouchEvent的返回值是true。

  B、如果A中的前提条件有一个不成立,那么就会执行view的onTouchEvent方法,此时dispatchTouchEvent的返回值由onTouchEvent的返回值决定。

  C、由onTouchEvent和performClick的源码可知,OnClickListener是在onTouchEvent中调用的,所以OnTouchListener会先于OnClickListener执行,如OnTouchListene返                  回true,就不会在调用OnClickListener了。

  D、如果view的Enabled为false,dispatchTouchEvent里就不会在调用OnTouchListener了(view不会响应OnTouchListener事件了),onTouchEvent就直接返回,不会继                  续往下执行了,OnClickListener也不会执行了(view不会响应OnClickListener事件)。此时onTouchEvent的返回值由Clickable决定了,如果Clickable是true就返回                   true,否则返回false。

  E、如果view的Enabled为true,并且走进了onTouchEvent方法中,那么onTouchEvent方法的返回值仍然是由Clickable决定的。

  F、Enabled会影响是否响应OnTouchListener和OnClickListener事件,Clickable会影响onTouchEvent的返回值。

  J、在view的setOnClickListener方法中会把Clickable设置为true,会影响Clickable的值。

  H、在down的时候,dispatchTouchEvent返回了false,就不会在接受到后面的MOVE、UP等其他事件了。



六、viewGroup的dispatchTouchEvent源码分析:

      
    @Override
    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);
        }

        //handled记录最终的返回结果,也就是是否消费
        boolean handled = false;

        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            //如果是ACTION_DOWN事件,进行初始化和重置
            // Handle an initial down.
            if (actionMasked == MotionEvent.ACTION_DOWN) { //只有是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.

                //清空mFirstTouchTarget链表,将mFirstTouchTarget置为null
                cancelAndClearTouchTargets(ev);

                //重置了disallowIntercept对应的标志位,这个标志位决定了是否允许父view拦截事件
                resetTouchState();
            }

            //判断是否拦截的标志位,true拦截,false不拦截。
            // Check for interception.
            final boolean intercepted;

            //如果是ACTION_DOWN事件,或者是MOVE、UP等其他事件时mFirstTouchTarget不为空(即已经有消费事件的目标view了)就会走到if里面去判断是否拦截
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {

                //disallowIntercept可以通过requestDisallowInterceptTouchEvent方法来设置,如果设置为true,就是子view通知父View不要拦截该事件。
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

                //如果disallowIntercept为true,那么就不会执行onInterceptTouchEvent(ev)方法了,直接将intercepted设置为false。否则会判断onInterceptTouchEvent(ev)。
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.

                //如果不是down事件,并且没有消费事件的目标view(即mFirstTouchTarget为空),那就直接拦截下来,ViewGroup自己处理。
                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);
            }

            //判断是否是cancel事件
            // Check for cancelation.
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

             //控制多点触摸标志位
            // Update list of touch targets for pointer down, if needed.
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;

            //保存消费事件的目标View所对应的TouchTarget对象 
            TouchTarget newTouchTarget = null;

            //标志事件是否已经分发到了目标View中 
            boolean alreadyDispatchedToNewTouchTarget = 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;

                //只有ACTION_DOWN事件才会执行if里面的代码,其他事件不执行(后面的两个条件不需要关心),这里面的是down事件的递归分发,去寻找消费事件的目标view的操                    作
                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 (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;

                        //遍历子View,递归寻找消费该down事件的目标view。
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, 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;
                            }

                            //判断事件是否落在当前子view的区域内,如果没有就continue继续查找下个子view,如果落在了当前子view的区域内,就继续往下走
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }

                            //用于多点触摸
                            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中调用子view的dispatchTouchEvent继续向下分发,如果返回true,说明子view消费了该事件,就走if里面
                            //这个方法后面会介绍

                            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();

                                //将消费事件的子view添加到mFirstTouchTarget中
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);

                                //是否已经分发到了目标View中的标志设置为true
                                alreadyDispatchedToNewTouchTarget = true;

                                //找到了消费事件的目标子view就不需要在进行循环了,直接break跳出循环
                                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();
                    }

                    //正常情况下这段代码不会执行
                    //因为只有down事件才能走到这,根据newTouchTarget = addTouchTarget(child, idBitsToAssign)可知,newTouchTarget和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.pointerIdBits |= idBitsToAssign;
                    }
                }
            }

            //如果mFirstTouchTarget为空,说明没有找到消费事件的目标子view,那就走if里面直接自己处理事件
            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                //走到这里面,说明已经有处理事件的目标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;

                    //如果是down事件,那么if条件就会成立,直接返回true给上层(如果不是down事件,会把newTouchTarget置空,把alreadyDispatchedToNewTouchTarget设置为                         false)
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {

                        / /如果intercepted为true,也就是当前viewgroup拦截了事件,那么就会给子view分发一个cancel事件(dispatchTransformedTouchEvent里面)
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;

                        //将move、up等事件分发给子view,如果cancelChild为true,子view就会收到一个cancle事件,否则就是正常的move、up事件继续往下传递
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }

                        //如果intercepted为true就会销毁mFirstTouchTarget,这就是为什么父view拦截之后,目标子view就不会在收到后续事件了
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                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;
    }



    //上面很多地方用到了这个方法,就看一下这个方法,这个方法就是做事件递归分发的方法,方法里分为3块内容,一个是cancel、一个是多点触控、一个是down、move、up        等正常的事件分发
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        // Canceling motions is a special case.  We don't need to perform any transformations
        // or filtering.  The important part is the action, not the contents.
        final int oldAction = event.getAction();

        //如果cancel为true,就走到if里面,将事件设置为cancel事件分发下去,这就是为什么事件被拦截之后,之前处理过该事件的目标子View会收到cancel事件
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                //如果没有目标子view,这个cancel事件就自己处理,最终会调用view的dispatchTouchEvent(event)方法
                handled = super.dispatchTouchEvent(event);
            } else {
                //如果有目标子view,就将这个cancel事件递归分发下去
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }

        //多点触摸需要
        // Calculate the number of pointers to deliver.
        final int oldPointerIdBits = event.getPointerIdBits();
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
        // If for some reason we ended up in an inconsistent state where it looks like we
        // might produce a motion event with no pointers in it, then drop the event.
        if (newPointerIdBits == 0) {
            return false;
        }
        // If the number of pointers is the same and we don't need to perform any fancy
        // irreversible transformations, then we can reuse the motion event for this
        // dispatch as long as we are careful to revert any changes we make.
        // Otherwise we need to make a copy.
        final MotionEvent transformedEvent;
        if (newPointerIdBits == oldPointerIdBits) {
            if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    event.offsetLocation(offsetX, offsetY);

                    handled = child.dispatchTouchEvent(event);

                    event.offsetLocation(-offsetX, -offsetY);
                }
                return handled;
            }
            transformedEvent = MotionEvent.obtain(event);
        } else {
            transformedEvent = event.split(newPointerIdBits);
        }

        //正常的事件分发代码
        // Perform any necessary transformations and dispatch.
        if (child == null) {
            //如果没有目标子view,这个事件就自己处理
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            transformedEvent.offsetLocation(offsetX, offsetY);
            if (! child.hasIdentityMatrix()) {
                transformedEvent.transform(child.getInverseMatrix());
            }

    //如果有目标子view,就将这个事件递归分发下去
            handled = child.dispatchTouchEvent(transformedEvent);
        }

        // Done.
        transformedEvent.recycle();
        return handled;
    }









   









猜你喜欢

转载自blog.csdn.net/huideveloper/article/details/71348043