事件分发源码解析

本文源码基于6.0

一.Activity中的事件分发。

1.dispatchTouchEvent。

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

第一步:如果是Down事件,执行onUserInteraction()方法,该方法默认是空实现,我们暂且不需要管。
第二步:调用getWindow().superDispatchTouchEvent(ev)。

public Window getWindow() {
        return mWindow;
    }

Window是一个抽象类,在Activity的attach()方法中,我们可以看到这样一行代码

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
        ......
        mWindow = new PhoneWindow(this);
        ......
    }

PhoneWindow是Window的唯一实现类,所以getWindow().superDispatchTouchEvent(ev)调用的是PhoneWindow的superDispatchTouchEvent(ev)方法。

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

我们可以看到调用了mDecor.superDispatchTouchEvent(event),mDecor是DecorView 的实例,DecorView是PhoneWindow的内部类,继承FrameLayout,

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

DecorView继承FrameLayout,因此会调用FrameLayout的dispatchTouchEvent()方法,由于FrameLayout并没有实现dispatchTouchEvent()方法,因此最终调用的是ViewGroup中的dispatchTouchEvent()方法,这部分后面详细分析。
第三步:如果有View消费调该事件,即getWindow().superDispatchTouchEvent(ev)返回true,则返回true,如果没有View消费该事件,即Activity的根视图以及根视图的子视图都没有拦截该事件的话,即返回false,则调用Activity的onTouchEvent()方法,Activity自己处理。

public boolean onTouchEvent(MotionEvent event) {
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }
        return false;
    }

Window.java中的shouldCloseOnTouch方法

public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
        if (mCloseOnTouchOutside && event.getAction() ==     
                MotionEvent.ACTION_DOWN
                && isOutOfBounds(context, event) && peekDecorView() != null) {
            return true;
        }
        return false;
    }

mCloseOnTouchOutside是一个boolean变量,它是由Window的android:windowCloseOnTouchOutside属性值决定。
isOutOfBounds(context, event)是判断该event的坐标是否在当前的Activity之外。是的话,返回true;否则,返回false。
peekDecorView()返回当前的DecorView。
也就是说:如果设置了android:windowCloseOnTouchOutside属性为true,并且当前事件是ACTION_DOWN,而且点击发生在Activity之外,同时Activity还包含视图的话,则返回true;表示该点击事件会导致Activity的结束。

二.ViewGroup的事件分发

在上面的分析中我们说道getWindow().superDispatchTouchEvent(ev)最终会调到ViewGroup中的dispatchTouchEvent(event)方法中,省略部分代码。

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        ......
        boolean handled = false;
        // 第一步
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;
            //第二步
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }
            //第三步
            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 {
                intercepted = true;
            }

            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 (!canceled && !intercepted) {
                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.
                        final ArrayList<View> preorderedList = buildOrderedChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        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 (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }

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

我们一步一步来分析:
第一步:首先调用onFilterTouchEventForSecurity(ev)来判断是否要分发该事件,该方法的实现在View中。

public boolean onFilterTouchEventForSecurity(MotionEvent event) {
        //noinspection RedundantIfStatement
        if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
                && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
            // Window is obscured, drop this touch.
            return false;
        }
        return true;
    }

如果该View不是位于顶部,并且有设置属性使该View不在顶部时不响应触摸事件,则不分发该触摸事件,即返回false。 否则,则对触摸事件进行分发,即返回true。
第二步:如果是down事件,清空之前的状态。
这里有必要说一下mFirstTouchTarget,它是接受触摸事件的View所组成的单链表。

private static final class TouchTarget {
        private static final int MAX_RECYCLED = 32;
        private static final Object sRecycleLock = new Object[0];
        private static TouchTarget sRecycleBin;
        private static int sRecycledCount;

        public static final int ALL_POINTER_IDS = -1; // all ones

        // 被触摸的view
        public View child;

        // pointerIdBits是记录触摸事件的id信息(对于多指触摸而言)
        public int pointerIdBits;

        // The next target in the target list.
        public TouchTarget next;

        private TouchTarget() {
        }

        public static TouchTarget obtain(View child, int pointerIdBits) {
            final TouchTarget target;
            synchronized (sRecycleLock) {
                if (sRecycleBin == null) {
                    target = new TouchTarget();
                } else {
                    target = sRecycleBin;
                    sRecycleBin = target.next;
                     sRecycledCount--;
                    target.next = null;
                }
            }
            target.child = child;
            target.pointerIdBits = pointerIdBits;
            return target;
        }

        public void recycle() {
            synchronized (sRecycleLock) {
                if (sRecycledCount < MAX_RECYCLED) {
                    next = sRecycleBin;
                    sRecycleBin = this;
                    sRecycledCount += 1;
                } else {
                    next = null;
                }
                child = null;
            }
        }
    }
private void cancelAndClearTouchTargets(MotionEvent event) {
        if (mFirstTouchTarget != null) {
            ......
            for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
                resetCancelNextUpFlag(target.child);
                dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
            }
            clearTouchTargets();

            if (syntheticEvent) {
                event.recycle();
            }
        }
    }
private void resetTouchState() {
        clearTouchTargets();
        resetCancelNextUpFlag(this);
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        mNestedScrollAxes = SCROLL_AXIS_NONE;
    }
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
        final int oldAction = event.getAction();
        //如果cancel为true或者action为ACTION_CANCEL,设置事件为cancel,并将      
        //事件分发出去
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            //如果child为null,则调用super.dispatchTouchEvent(event),即View中的 
            //dispatchTouchEvent(event),如果不为null,则将该事件分发给子孩子。
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }

        final int oldPointerIdBits = event.getPointerIdBits();
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
        if (newPointerIdBits == 0) {
            return false;
        }
        final MotionEvent transformedEvent;
        // 如果计算得到的前后触摸事件id信息相同,则执行不需要重新计算 
        //MotionEvent,直接执行if语句块进行消费分发;
        // 否则,就重新计算MotionEvent之后,再进行消息分发。
        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);
        }
        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);
        }
        transformedEvent.recycle();
        return handled;
    }

清空mPrivateFlags的PFLAG_CANCEL_NEXT_UP_EVEN标记

private static boolean resetCancelNextUpFlag(View view) {
        if ((view.mPrivateFlags & PFLAG_CANCEL_NEXT_UP_EVENT) != 0) {
            view.mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
            return true;
        }
        return false;
    }

清空mFirstTouchTarget链表,并设置mFirstTouchTarget为null

private void clearTouchTargets() {
        TouchTarget target = mFirstTouchTarget;
        if (target != null) {
            do {
                TouchTarget next = target.next;
                target.recycle();
                target = next;
            } while (target != null);
            mFirstTouchTarget = null;
        }
    }

总结:cancelAndClearTouchTargets作用就是遍历mFirstTouchTarget链表,清空链表中的每一个view的PFLAG_CANCEL_NEXT_UP_EVENT标记。
第三步:是否需要拦截事件
如果是down事件或者mFirstTouchTarget不为null,执行if中的代码,首先判断是否禁止ViewGroup进行事件拦截,检查FLAG_DISALLOW_INTERCEPT标记,如果调用了requestDisallowInterceptTouchEvent()标记的话,则FLAG_DISALLOW_INTERCEPT会为true。
如果disallowIntercept为true的话,则intercepted = fasle,反之调用onInterceptTouchEvent(ev)方法

   public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false;
    }

第四步:检查当前事件是否取消

final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

对于ACTION_DOWN来说,mPrivateFlags的PFLAG_CANCEL_NEXT_UP_EVENT位肯定是0;因此,canceled=false。
当前的View或ViewGroup要被从父View中detach时,PFLAG_CANCEL_NEXT_UP_EVENT就会被设为true;此时,它就不再接受触摸事件。
第五步:将事件分发给子view。

       TouchTarget newTouchTarget = null;
       boolean alreadyDispatchedToNewTouchTarget = false;
       //如果事件没有被取消并且事件没有被拦截,则将事件分发给子view
       if (!canceled && !intercepted) {
                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                        ? findChildWithAccessibilityFocus() : null;
                //事件为ACTION_DOWN
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    //对于down来说始终为0.
                    final int actionIndex = ev.getActionIndex(); // always 0 for down
                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;

                    removePointersFromTouchTargets(idBitsToAssign);

                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        final ArrayList<View> preorderedList = buildOrderedChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        //从后向前遍历
                        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 (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }
                            //如果该child可以接受事件(该child是VISIBLE的或者该child不是    
                           //VISIBLE的,但是位于动画状态)并且触摸左边落在child的可视范 
                           //围内,则继续执行,否则continue。
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }
                            //查找child是否存在于mFirstTouchTarget链表中
                            newTouchTarget = getTouchTarget(child);
                            //如果存在跳出for循环
                            if (newTouchTarget != null) {
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }
                           // 重置child的mPrivateFlags变量中的 
                   PFLAG_CANCEL_NEXT_UP_EVENT位。
                            resetCancelNextUpFlag(child);
                           //将事件分发给子view
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                                    for (int j = 0; j < childrenCount; j++) {
                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                //如果该child可以接受该事件,即该child拦截了该事件或者消费 
                                //了该事件并返回true,将该view添加到mFirstTouchTarget链表表头
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                               //将alreadyDispatchedToNewTouchTarget置为true。
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
                            ev.setTargetAccessibilityFocus(false);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }
                    //如果newTouchTarget == null并且mFirstTouchTarget != null,将mFirstTouchTarget中第一个不为null的节点设置给newTouchTarget
                    if (newTouchTarget == null && mFirstTouchTarget != null) {
                        newTouchTarget = mFirstTouchTarget;
                        while (newTouchTarget.next != null) {
                            newTouchTarget = newTouchTarget.next;
                        }
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }

第六步:进一步进行事件分发

            if (mFirstTouchTarget == null) {
                //如果mFirstTouchTarget == null,意味着没有子孩子可以接受该事件,调用dispatchTransformedTouchEvent方法,注意传的参 
                //数,第三个参数为null,则会调用super.dispatchTouchEvent(event),也就是调用View.dispatchTouchEvent(event),由于ViewGroup没有覆盖onTouchEvent(event),最终会调用View.onTouchEvent(event)。
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                //如果如果mFirstTouchTarget != null,说明又可以接受事件的子孩子。
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        //如果alreadyDispatchedToNewTouchTarget为true(说明已经分发过),并且target等于newTouchTarget,则直接返回true
                        handled = true;
                    } else {
                        //否则分发事件给子view,主要针对MOVE和UP事件
                        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;
                }
            }

总结一下:
down事件时,相当于一次事件的开始,会清空之前的标记和mFirstTouchTarget链表。
ViewGroup默认不拦截事件。
子元素可以通过调用ViewGroup的requestDisallowInterceptTouchEvent方法修改FLAG_DISALLOW_INTERCEPT标志位的值请求不要拦截该事件,但ACTION_DOWN事件除外,因为在ACTION_DOWN事件FLAG_DISALLOW_INTERCEPT标志位会被重置。
第五步中遍历子孩子并将事件分发给可以接受事件的子孩子,注意只有down事件时才会执行第五步。此时会把可以接受事件的子孩子保存到
mFirstTouchTarget链表中。
如果某个子孩子没有接受down事件,即该子孩子没有保存到
mFirstTouchTarget链表中,那么该子孩子也不会接受到move和up事件。

三.View的事件分发。

由上面的逻辑可知,最终会调用到View的dispatchTouchEvent()方法。

public boolean dispatchTouchEvent(MotionEvent event) {
        ......
        boolean result = false;
         //如果该View不是位于顶部,并且有设置属性使该View不在顶部时不响应触摸事件,则不分发该触摸事件,即返回false。 否则,则对触摸事件进行分发,即返回true。
        if (onFilterTouchEventForSecurity(event)) {
            ListenerInfo li = mListenerInfo;
            //如果li不为null、并且设置了OnTouchListener、并且View是可点击的、并且OnTouchListener.onTouch()方法返回true,则返回true。
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
            //调用onTouchEvent(event),如果onTouchEvent(event)返回true,则返回true,否则返回false。
            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();
        //当view的状态为DISABLED(被禁用,调用setEnabled(false)时,View就被禁用了)时,返回它是否时可点击的(仍然会消费掉事件,只是没有效果而已)
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            return (((viewFlags & CLICKABLE) == CLICKABLE
                    || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
        }
        //当我们设置了TouchDelegate监听代理时,会调用mTouchDelegate.onTouchEvent(event),类似于扩大点击范围时,mTouchDelegate默认情况下为null。
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
        //如果不可点击(既不能单击,也不能长按)则直接返回false
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
            switch (action) {
                // 下面事件分开讲解
            }

            return true;
        }

        return false;
    }

case MotionEvent.ACTION_DOWN:

case MotionEvent.ACTION_DOWN:
        //处理长按事件标识
        mHasPerformedLongPress = false;

        if (performButtonActionOnTouchDown(event)) {
               break;
        }
        //判断是否正在滚动的容器中,不能把滑动当前点击.所以先判断是不是在一个可滑动的容器中
        boolean isInScrollingContainer = isInScrollingContainer();
        if (isInScrollingContainer) {
           //如果是在一个可滚动的容器中,先设置用户准备点击这么一个标志位:PFLAG_PREPRESSED,然后则发送一个延迟消息来确定用户到底是要滚动还是点击
            mPrivateFlags |= PFLAG_PREPRESSED;
            if (mPendingCheckForTap == null) {
                  mPendingCheckForTap = new CheckForTap();
            }
            mPendingCheckForTap.x = event.getX();
            mPendingCheckForTap.y = event.getY();
            //在给定的tapTimeout时间之内,用户的触摸没有移动,就当作用户是想点击,而不是滑动
            postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
         } else {
             //如果不是在一个可滚动的容器中,调用setPressed(true) 设置按下状态.,setPressed 主要是设置PFLAG_PRESSED标志位,检查长按。
             setPressed(true, x, y);
             checkForLongClick(0);
         }
         break;

检查点击还是滑动的具体做法:将 CheckForTap的实例mPendingCheckForTap添加时消息队例中,延迟执行.
如果在这tagTimeout之间用户触摸移动了,则删除此消息.否则:执行按下状态.然后检查长按,检查长按事件的思路也类似。
CheckForTap消息方法如下:

private final class CheckForTap implements Runnable {
        public float x;
        public float y;

        @Override
        public void run() {
            mPrivateFlags &= ~PFLAG_PREPRESSED;
            setPressed(true, x, y);
            checkForLongClick(ViewConfiguration.getTapTimeout());
        }
    }

case MotionEvent.ACTION_MOVE:

case MotionEvent.ACTION_MOVE:
        drawableHotspotChanged(x, y);
        //判断触摸点是否在此view中,先将上下左右增大mTouchSlop个像素,再判断。
        if (!pointInView(x, y, mTouchSlop)) {
              //如果超出view之外,将处理点击消息移除
             removeTapCallback();
             if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                  //如果是已经准备长按了,则将长按的消息移除.并将View的按下状态设置为false。
                 removeLongPressCallback();
                 setPressed(false);
             }
         }
        break;

case MotionEvent.ACTION_UP:

case MotionEvent.ACTION_UP:
       boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
       //首先是检查 PFLAG_PREPRESSED 和PFLAG_PRESSED 这两个标志.如果其中一个为真则处理。
       //这两个标志位首先是在开始触控时(即手指按下ACTION_DOWN)时设置,PFLAG_PREPRESSED 表示在一个可滚动的容器中,要稍后才能确定是按下还是滚动,PFLAG_PRESSED 表示不是在一个可滚动的容器中,已经可以确定按下这一操作。
       if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
           boolean focusTaken = false;
           if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
              focusTaken = requestFocus();
           }

           if (prepressed) {
              setPressed(true, x, y);
           }
            //判断是否进行了长按,如果没有,则移除长按
           if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
               removeLongPressCallback();
               //判断有没有重新请求获得焦点,如果还没有新获得焦点,说明之前已经是按下的状态了。
               if (!focusTaken) {
                  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)) {
                  mUnsetPressedState.run();
             }

             removeTapCallback();
        }
        mIgnoreNextUpEvent = false;
        break;
private final class PerformClick implements Runnable {
        @Override
        public void run() {
            performClick();
        }
    }
public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        //如果li不为null并且设置了OnClickListener,则执行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;
    }

case MotionEvent.ACTION_CANCEL:

 //重置按钮状态及变量的值,移除点击和长按的检查
  case MotionEvent.ACTION_CANCEL:
       setPressed(false);
       removeTapCallback();
       removeLongPressCallback();
       mInContextButtonPress = false;
       mHasPerformedLongPress = false;
       mIgnoreNextUpEvent = false;
       break;

好了,事件分发的源码分析就到此结束了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容