导语:

在我们使用Android做一些控件的滑动和点击时,各种冲突事件、点击事件无响应等一些touch事件无响应困扰着我们,今天我将从源码角度分析android的事件分发机制

1.简单看下例子(搞清楚onTouch和onClick的关系):


    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        testViewGroup  =(TestViewGroup)findViewById(R.id.testViewGroup);        testViewGroup.setOnTouchListener(new View.OnTouchListener() {            @Override            public boolean onTouch(View v, MotionEvent event) {                Log.i(TAG,"MainActivity +++ onTouch");                return true;            }        });        testViewGroup.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                Log.i(TAG,"MainActivity +++ onClick");            }        });    }

当onTouch返回值为true和false两种不同的情况
请想下输出的onTouch事件还有onClick事件的打印结果?
当为true,结果为:

1.png
当为false,结果为:
2.png
看到这里是否有疑问?接下来带大家一步一步分析:

首先点开View.java文件 找到:dispatchTouchEvent()方法
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);     }     //关键值,用于判断onTouchEvent()是否该执行     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)) {         if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {             result = true;         }         //判断onTouch返回值是否为true,如果为true,resulet也为true         ListenerInfo li = mListenerInfo;         if (li != null && li.mOnTouchListener != null                 && (mViewFlags & ENABLED_MASK) == ENABLED                 && li.mOnTouchListener.onTouch(this, event)) {             result = true;         }         //为true 不执行onTouchEvent()         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; }

li.mOnTouchListener中存放的是该View的onTouch监听,所以onTouch返回true时, result = true; 不执行onTouchEvent(event),所以打印时没有onTouchEvent的log日志,当onTouch返回false时,接下来进入onTouchEvent()方法,打印了onTouchEvent的log日志。

接下来找到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();        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;            }        }        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);                       }                        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)) {                                    //这里执行onClick正真的点击事件的方法                                    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, x, y);                    }                    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;    }

只要看关键代码,找到 performClick()方法。

在找到performClick()方法
   public boolean performClick() {        final boolean result;        final ListenerInfo li = mListenerInfo;        //如果监听类中的li.mOnClickListener不为空,则执行它的onClick方法        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;    }

这里就是最后的主角登场了onClick()方法在这里执行。
是不是之前的疑惑迎刃而解了。

其实前面的打印截图留有一丝伏笔:
再看截图:


1.png
2.png

*点击事件是怎么从MainActivity的 dispatchTouchEvent传给TestViewGroup的dispatchTouchEvent?
*TestViewGroup的dispatchTouchEvent又是怎么传给onInterceptEvent?
带着疑问我们进入第二节。

2.触摸事件如何传递:


前言:

从之前的截图可以知道事件先从MainActivity的dispatchTouchEvent开始的。具体为什么是从MainActivity的dispatchTouchEvent开始这篇文章不会涉及到,将会在以后的文章中详细解释。

1.MainActivity的 dispatchTouchEvent传给TestViewGroup的dispatchTouchEvent

   @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        Log.i(TAG,"MainActivity +++ dispatchTouchEvent");        return super.dispatchTouchEvent(ev);    }
点开super.dispatchTouchEvent(ev);

将会进入Activity的dispatchTouchEvent方法:

  public boolean dispatchTouchEvent(MotionEvent ev) {        if (ev.getAction() == MotionEvent.ACTION_DOWN) {            onUserInteraction();        }        if (getWindow().superDispatchTouchEvent(ev)) {            return true;        }        return onTouchEvent(ev);    }

我们只看getWindow().superDispatchTouchEvent(ev)方法。原来dispatchTouchEvent执行的是getWindow().superDispatchTouchEvent(ev),这个方法是干什么的呢?就是将事件传递给Tree View布局控件。继续点进入superDispatchTouchEvent方法发现只是Window抽象接口。window的实现类其实是PhoneWindow,至于为什么,有了解的朋友应该知道,不知道的朋友可以去查下,下篇文章会讲到。通过文件查找找到PhoneWindow的superDispatchTouchEvent()方法.

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

发现它还是调用的自己的内部类DecorView的mDecor.superDispatchTouchEvent(event),顺便提下DecorView是整个视图组成的根视图,继续往下走:

   public boolean superDispatchTouchEvent(MotionEvent event) {        return super.dispatchTouchEvent(event);    }

发现它并未有实现这个superDispatchTouchEvent()方法而是调用父类的dispatchTouchEvent方法。继续点进去,发现直接进入到ViewGroup的dispatchTouchEvent方法,并没有进入DecorView的父类FrameLayout,而是直接抛给FrameLayout的父类ViewGroup,所有就是将事件传递Tree View最近的并且实现了ViewGroup的控件,TestViewGroup继承自ViewGroup,并且是最上层。
所有这里就可以解释触摸事件是怎么从MainActivity的 dispatchTouchEvent传给TestViewGroup的dispatchTouchEvent。

2.分析ViewGroup的dispatchTouchEvent方法

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);        }        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) {                // 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.             //第二步判断是否拦截            //  第二次move触发时 压根不会遍历子控件            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;            }            // 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);            }            // 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;            TouchTarget newTouchTarget = null;            boolean alreadyDispatchedToNewTouchTarget = false;            //在该if中做真正事件分发相关操作            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;                    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.                        //child重新排序                        final ArrayList preorderedList = buildTouchDispatchChildList();                        final boolean customOrder = preorderedList == null                                && isChildrenDrawingOrderEnabled();                        final View[] children = mChildren;                        for (int i = childrenCount - 1; i >= 0; i--) {                            final int childIndex = getAndVerifyPreorderedIndex(                                    childrenCount, i, customOrder);                            //preorderedList.get(childIndex)等价于                            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  条件:clickable invisiable 点击事件 不在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;                            }                            /**                             * 真正做事件分发                             * child 不为空                             */                            resetCancelNextUpFlag(child);                            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();                                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();                    }                    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;                    }                }            }            // 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 {                // 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;                        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;                }            }            // 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;    }

我们只分析重要部分。
首先第一步初始化状态:当手指第一次按下的时候它会先进入cancelAndClearTouchTargets(ev)方法

   /**     * Cancels and clears all touch targets.     * 清除标志     */    private void cancelAndClearTouchTargets(MotionEvent event) {        if (mFirstTouchTarget != null) {            boolean syntheticEvent = false;            if (event == null) {                final long now = SystemClock.uptimeMillis();                event = MotionEvent.obtain(now, now,                        MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);                event.setSource(InputDevice.SOURCE_TOUCHSCREEN);                syntheticEvent = true;            }            for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {                resetCancelNextUpFlag(target.child);                dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);            }            clearTouchTargets();            if (syntheticEvent) {                event.recycle();            }        }    }

你可以看到如果mFirstTouchTarget不为null的时候执行会执行括号里的方法,最后会执行clearTouchTargets(),进入clearTouchTargets()可以看到就是执行清空操作

  private void clearTouchTargets() {        TouchTarget target = mFirstTouchTarget;      //进行while循环清除存在的target标记        if (target != null) {            do {                TouchTarget next = target.next;                target.recycle();                target = next;            } while (target != null);          //将第一次标记清空            mFirstTouchTarget = null;        }    }

回到dispatchTouchEvent()方法,接下来看第二方法 resetTouchState():

  private void resetTouchState() {        clearTouchTargets();        resetCancelNextUpFlag(this);        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;        mNestedScrollAxes = SCROLL_AXIS_NONE;    }

发现该方法中还进行了一次clearTouchTargets()方法,防止没有清空的标记,之后将所有的接触状态制为最初始状态,准备重新开始新的事件。
接下来第二步:判断是否被拦截。

    // Check for interception. //是否拦截            //  第二次move触发时 压根不会遍历子控件            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;            }

在这块代码中,会先判断disallowIntercept是否为false,如果为false就会进入到拦截方法onInterceptTouchEvent(ev)

补充:

mGroupFlags的值可以通过requestDisallowInterceptTouchEvent()设置。如果在TestViewGroup或者其子View中设置了requestDisallowInterceptTouchEvent(true)则TestViewGroup就不会执行onInterceptTouchEvent()方法

 @Override    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {        if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {            // We're already in this state, assume our ancestors are too            return;        }        if (disallowIntercept) {            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;        } else {            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;        }        // Pass it up to our parent        if (mParent != null) {            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);        }    }

接下来继续分析:disallowIntercept为false 则会进入onInterceptTouchEvent()方法。onInterceptTouchEvent()方法我们在TestViewGroup中重写实现,如果重现后的onInterceptTouchEvent放回为true,则intercepted = true,则不会进入到真正的事件分发中,也就是事件不能传递给子View。

分析onInterceptTouchEvent为true的情况。

接下来判断这里:

  if (mFirstTouchTarget == null) {                // No touch targets so treat this as an ordinary view.                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) {                        handled = true;                    } else {                        final boolean cancelChild = resetCancelNextUpFlag(target.child)                                || intercepted;                        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;                }            }

因为我们开始的初始化状态已经把mFirstTouchTarget置空了。所以这里进入dispatchTransformedTouchEvent(ev,canceled,null,TouchTarget.ALL_POINTER_IDS);。点击进去这个方法。

      // Perform any necessary transformations and dispatch.        // 第一次次 接下来  被拦截        if (child == null) {            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());            }            handled = child.dispatchTouchEvent(transformedEvent);        }        // Done.        transformedEvent.recycle();        return handled;

找到最后这部分代码。这里是事件传递的地方,因为child的值为null,所以进入到
handled = super.dispatchTouchEvent(transformedEvent);
点开dispatchTouchEvent方法,你会发现这里跳到之前我们分析View的dispatchTouchEvent()方法,所以结合最上面的分析如果没做其它的onTouch操作,则会顺利的进入到onTouchEvent(event)方法。到这里是否明白我们实现了拦截方法后事件不会传递到子View而是直接跳转到了onTouchEvent(event)让后结束。最后返回到MainActivity中,当onTouchEvent()返回值会决定MainActivity会不会调用MainActivity自己的onTouchEvent(event)方法。

分析onInterceptTouchEvent为false的情况。

当为false则会进入到ViewGroup.dispatchTouchEvent方法中的判断中:

    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);                    //判断是否有子View                    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.                        //<1>child重新排序                        final ArrayList preorderedList = buildTouchDispatchChildList();                        final boolean customOrder = preorderedList == null                                && isChildrenDrawingOrderEnabled();                        final View[] children = mChildren;                      //<2>事件分发   遍历子控件                        for (int i = childrenCount - 1; i >= 0; i--) {                            final int childIndex = getAndVerifyPreorderedIndex(                                    childrenCount, i, customOrder);                            //preorderedList.get(childIndex)等价于                            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  条件:clickable invisiable 点击事件 不在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);                            /**                             * 真正做事件分发                             * child 不为空                             */                                                  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();                                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();                    }                    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;                    }                }            }

<1>先会判断是否有子View遍历子View,让后将这些View重新排序,为什么要进行重新排序?是因为多个子View之间会存在重叠的现象,点在重叠位置的时候事件是可以穿透的,谁先接收到事件?谁该执行?在这之前的顺序并不知道。所以就需要先将子View重排序,按照重排序之后的顺序执行
<2>遍历子View,先判断子View能否接收,clickable、invisible、点击事件、不在view范围中这几种情况是不能接收那就直接放回。让后执行能接收到事件的View

   newTouchTarget = getTouchTarget(child);

这里是找到直接接收事件的子View,因为第一次点击newTouchTarget=null,所以不执行下面if判断。但如果是手指移动的时候 就可以找到接收事件的子View,就不用继续遍历。
让后在dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)做事件分发,这时候子View child不为空。继续分析dispatchTransformedTouchEvent中的child不为空的方法。

      // Perform any necessary transformations and dispatch.        // 第一次次 接下来  被拦截        if (child == null) {            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());            }            handled = child.dispatchTouchEvent(transformedEvent);        }        // Done.        transformedEvent.recycle();        return handled;

为什么这里需要 final float offsetX = mScrollX - child.mLeft;final float offsetY = mScrollY - child.mTop;因为offsetX是子View相对屏幕边的X方向的距离。怎么得到?mScrollX是ViewGroup距离子控件的距离,ViewGroup的边可能在屏幕外面也可能在屏幕里面,child.mLeft是ViewGroup距离屏幕边的距离,当ViewGroup超出屏幕child.mLeft的值是正的相减,当ViewGroup没有超出屏幕child.mLeft的值是负的相加,offsetY同理。
让后执行transformedEvent.transform,这里是得到真正的相对屏幕距离
最后执行子View的dispatchTouchEvent(transformedEvent)。到这里又和上面分析的情况一样了。最后如果子View的onTouchEvent()有返回true,子View则会被添加到newTouchTarget链表中,让后结束遍历;如果子View的onTouchEvent()的返回值都为false则newTouchTarget就没有子View添加,newTouchTarget=null。
ViewGroup的dispatchTouchEvent后执行到这里:

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

mFirstTouchTarget到时候就不等于空了,到else里面,这里又进行了一次dispatchTransformedTouchEvent()方法,target.child 不为空则handled=true最后也返回值也是true,如果target.child为空则根据上面分析的dispatchTransformedTouchEvent()方法又会到super.dispatchTouchEvent(transformedEvent);让后执行自己的onTouchEvent()方法
最后的到一张图。


3.png

这里分析的是down事件,当滑动的时候,不会再进入到循环遍历里面了,如果之前已经拿到childe,newTouchTarget不为空则直接将事件传递给了相应的子View,不用在做遍历判读了。这就谷歌工程师优化的地方。

结束语:

总算是分析完了,分析时候大致思路就是这样,可能其中有一些没说清楚的,希望大家提给我,我会努力改好的

更多相关文章

  1. 浅谈Java中Collections.sort对List排序的两种方法
  2. Python list sort方法的具体使用
  3. python list.sort()根据多个关键字排序的方法实现
  4. android上一些方法的区别和用法的注意事项
  5. android实现字体闪烁动画的方法
  6. Android中dispatchDraw分析
  7. Android四大基本组件介绍与生命周期
  8. Android(安卓)MediaPlayer 常用方法介绍
  9. 在Fragment中设置控件点击方法,执行失败。

随机推荐

  1. android获取sdk更新
  2. android
  3. android学习小结2
  4. 利用HTML5开发Android
  5. android AsyncTask类的使用
  6. 学习 Android(安卓)Handler 消息机制需要
  7. Android官方DrawerLayout 抽屉式侧滑菜单
  8. Android中onInterceptTouchEvent与onTouc
  9. Android(安卓)ListView 设置分割线的设置
  10. Android调试笔记——FATAL EXCEPTION: ma