在了解分发机制之前,需要了解什么是触摸事件,在OOM中,触摸事件被封装成了一个类,这个类就是MotionEvent。事件分发机制就是对于MotionEvent的分发机制。

MotionEvent

四种事件:
1、MotionEvent.ACTION_DOWN
2、MotionEvent.ACTION_MOVE
3、MotionEvent.ACTION_UP
4、MotionEvent.ACTION_CANCLE
看名字都能知道分别是,按下、移动、抬起、取消。可能只有取消比较难理解,举一个平时使用的例子,如果用户点击一个Button,但是按下之后将手指ACTION_MOVE到Button的视图之外,那这个Button是不会被点击的,这就是ACTION_CANCLE。

传递层级

从ACTION_DOWN开始,MotionEvent的传递一般会经过以下层级。:
Activity→Window→DecorView→ViewGroup→View

那就一级一级往下说呗。

在Activity中,事件分发机制有两个主要的方法:
dispatchTouchEvent()
onTouchEvent()

    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        return super.dispatchTouchEvent(ev);    }

可以看到在dispatchTouchEvent()中调用了super.dispatchTouchEvent(ev),那我们再点进去看一下。

    public boolean dispatchTouchEvent(MotionEvent ev) {        if (ev.getAction() == MotionEvent.ACTION_DOWN) {        //默认是一个空方法,没有重写的情况下暂时不去管它            onUserInteraction();        }        //1        if (getWindow().superDispatchTouchEvent(ev)) {            return true;        }        return onTouchEvent(ev);    }

可以看到在注解1处,MotionEvent已经从Activity传递到了Window中,调用了getWindow().superDispatchTouchEvent(ev)。
那么Activity是在什么时候初始化了一个PhoneWindow的呢,那就需要查看源码了。我们可以在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,            Window window, ActivityConfigCallback activityConfigCallback) {        attachBaseContext(context);        mFragments.attachHost(null /*parent*/);        mWindow = new PhoneWindow(this, window, activityConfigCallback);//清晰可见地初始化了PhoneWindow        ///       }

如果该方法返回为true,则return true,否则调用Activity的onTouchEvent()方法来消费掉事件。不出意外,应该就能在这个方法中找到DecorView,我们继续点击下去。

    /**     * Used by custom windows, such as Dialog, to pass the touch screen event     * further down the view hierarchy. Application developers should     * not need to implement or call this.     *     */    public abstract boolean superDispatchTouchEvent(MotionEvent event);

发现是一个抽象方法,也不要紧,我们拉到Window类的文件开始的描述处。

/** * Abstract base class for a top-level window look and behavior policy.  An * instance of this class should be used as the top-level view added to the * window manager. It provides standard UI policies such as a background, title * area, default key processing, etc. * * 

The only existing implementation of this abstract class is * android.view.PhoneWindow, which you should instantiate when needing a * Window. */

其他的话先忽略,我们就看一句话:The only existing implementation of this abstract class is
android.view.PhoneWindow,问题简单了,我们去这个类看看对应的方法。

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

显而易见,DecorView已经出现了,那么现在会有个问题,就是DecorView到底是个什么东西?

    // This is the top-level view of the window, containing the window decor.    private DecorView mDecor;

翻译一下就是说这个DecorView是最高层的视图。DecorView是整个ViewTree的最顶层View,它是一个FrameLayout布局,代表了整个应用的界面。这一看是不是就能猜出来是在这个mDecor是在setContentView()方法初始化的呢。

回到我们的传递层级线路上来,调用了mDecor.superDispatchTouchEvent(event)。

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

继续点下去然后我们就会发现自己进到了ViewGroup的源码当中,并且这个super.dispatchTouchEvent(event)有200多行。那个……我先贴在下面,等会再解释。至少到了这一步,从Activity分发到ViewGroup的传递过程是捋清楚了。

ViewGroup

ViewGroup有三个重要方法:
dispatchTouchEvent()
onInterceptTouchEvent()
onTouchEvent()

    @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.            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 (!canceled && !intercepted) {                // If the event is targeting accessibility 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.                        final ArrayList<View> 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);                            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;                            }                            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;    }

虽然代码很长,但是总结一下其实就做了三件事情:

  • 1、判断是否拦截事件
  • 2、在当前ViewGroup中找到用户真正点击的View
  • 3、分发事件至指定View

ViewGroup拥有三个控制MotionEvent分发的方法:
dispatchTouchEvent()如果事件进入到ViewGroup中,则该方法会被调用到。
onInterceptTouchEvent()会在事件分发的过程中调用,如果返回值为true,则会拦截掉该事件;若为false则不会拦截。
onTouchEvent()这个方法在ViewGroup没有实现,这个方法的实现在View中。

这几个点了解了,那我们就照着源码一点一点往下啃呗。

//1        if (mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);        }//2        // 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);        }

注释1处是调试对象的判定,不是重点忽略。
注释2处只是一个辅助功能的选项,不会影响整体逻辑,忽略不管。

boolean handled = false;//1        if (onFilterTouchEventForSecurity(ev)) {            final int action = ev.getAction();            final int actionMasked = action & MotionEvent.ACTION_MASK;            ...

判断当前MotionEvent是否符合安全策略,那什么是安全策略呢?
简单来说就是按照用户的使用习惯,用户只会点击看得到的View或者ViewGroup。
如果某个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;    }

如果MotionEvent不符合安全策略的话就会直接跳转到方法的最底部然后return handled的默认值也就是false了。
如果符合了安全策略,继续往下判断:

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

每次用户与屏幕的交互都是以ACTION_DOWN为开始。
该判断里面就是负责初始化,具体实现就不展开解释了。从注释里也能了解到,Throw away all previous state when starting a new touch gesture.

            // Check for interception.            final boolean intercepted;            if (actionMasked == MotionEvent.ACTION_DOWN            //1                    || mFirstTouchTarget != null) {                    //2                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;                if (!disallowIntercept) {                //3                    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;            }

到了这一步就是判断事件是否需要拦截。
注释1处的mFirstTouchTarget是事件首次触发的目标。当其不等于空时则表示已经存在处理事件的子View了。
注释2处的disallowIntercept 代表是否允许拦截,如果为true则代表不允许被拦截;为false则代表有可能会去拦截一个事件。
注释3处能看到一个很熟悉的方法呀onInterceptTouchEvent(),现在就能知道为什么该方法返回的值会导致事件的拦截了吧。

    public boolean onInterceptTouchEvent(MotionEvent ev) {        if (ev.isFromSource(InputDevice.SOURCE_MOUSE)                && ev.getAction() == MotionEvent.ACTION_DOWN                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)                && isOnScrollbarThumb(ev.getX(), ev.getY())) {            return true;        }        return false;    }

也能看出来,只有在这四个条件都满足的情况下才会返回true。一般都是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;

cancled表示是否取消了触摸事件。
split表示是否存在多个重叠子视图,如果为true的话则多个重叠子视图都可以获得触摸事件。
接下来若时间不是一个取消事件且未被拦截的话,就会进入到事件分发的代码中。

                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()                        ? findChildWithAccessibilityFocus() : null;

该View是在辅助功能下用来接收该事件目标的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);//的到当前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.                        //可以接收到触摸事件的子View的集合                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();                        //判断是否自定义了绘制顺序,为true则为自定义,false为无自定义。该变量对于获取真实索引的时候起到关键作用。                        final boolean customOrder = preorderedList == null                                && isChildrenDrawingOrderEnabled();                        final View[] children = mChildren;                        //这一步开始尝试找到用户真正点击到的View                        for (int i = childrenCount - 1; i >= 0; i--) {                        //获得索引                            final int childIndex = getAndVerifyPreorderedIndex(                                    childrenCount, i, customOrder);                        //获取该索引代表的View                            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能否接收到触摸事件                            if (!canViewReceivePointerEvents(child)                            //判断该触摸事件是否在当前View的范围之内                                    || !isTransformedTouchPointInView(x, y, child, null)) {                                    //只有当两个都返回为true才意味着找到了触摸事件的对象                                ev.setTargetAccessibilityFocus(false);                                continue;                            }//newTouchTarget为触摸对象                            newTouchTarget = getTouchTarget(child);                            //如果成功找到了触摸对象,则直接break                            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;                            }//能运行到这一步则代表没有找到触摸事件的接收对象,下一个方法是判断该View是否设置了暂时不要接收触摸事件的标志位,如果有就会清除该标志位                            resetCancelNextUpFlag(child);                            //进行了下一层的事件分发,该方法很重要,描述了事件在ViewGroup到View中是如何具体过渡的。如果返回值为true,则代表触摸事件已经分发到了子View的dispatchTouchEvent方法,且dispatchTouchEvent返回为true,代表事件已经被消费。                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {                                // Child wants to receive touch within its bounds.                                mLastTouchDownTime = ev.getDownTime();                                if (preorderedList != null) {                                    // childIndex points into presorted list, find original index                                    for (int j = 0; j < childrenCount; j++) {                                        if (children[childIndex] == mChildren[j]) {                                            mLastTouchDownIndex = j;                                            break;                                        }                                    }                                } else {                                    mLastTouchDownIndex = childIndex;                                }                                mLastTouchDownX = ev.getX();                                mLastTouchDownY = ev.getY();                                //在该方法中对mFirstTouchTarget赋值,如果mFirstTouchTarget不为空则代表存在子View                                newTouchTarget = addTouchTarget(child, idBitsToAssign);                                alreadyDispatchedToNewTouchTarget = true;                                break;                            }                            // The accessibility focus didn't handle the event, so clear                            // the flag and do a normal dispatch to all children.                            ev.setTargetAccessibilityFocus(false);                        }                        //释放资源                        if (preorderedList != null) preorderedList.clear();                    }

上述代码其实核心功能就是找到接收触摸事件的子View并将触摸事件分发给子View。

//如果还是没有找到触摸事件的目标Viewif (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;                    //alreadyDispatchedToNewTouchTarget 在之前循环赋值,该变量为了避免触摸事件被重复处理                    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;                }            }

ViewGroup的分发源码就分析到这里,总结一下还是开头那三件事:

  • 1、判断是否拦截事件
  • 2、在当前ViewGroup中找到用户真正点击的View
  • 3、分发事件至指定View

View

关键方法有两个:
dispatchTouchEvent()
onTouchEvent()
当触摸事件分发到了View上的时候,dispatchTouchEvent方法会被立刻执行,大致执行流程为:
判断是否有可响应焦点→判断是否符合安全策略→判断是否为鼠标事件→是否注册onTouchListener并监听onTouch返回值→监听onTouchEvent返回值。
直接开啃好吧。

    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)) {        //判断当前事件是否是通过鼠标进行滚动条的拖动            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {                result = true;            }            //noinspection SimplifiableIfStatement            //对事件监听的判断            ListenerInfo li = mListenerInfo;            //li不为空且li.mOnTouchListener不为空则标志着当前View注册了onTouchListener接口。            if (li != null && li.mOnTouchListener != null                    && (mViewFlags & ENABLED_MASK) == ENABLED                    //调用了onTouch方法且方法返回为true                    && li.mOnTouchListener.onTouch(this, event)) {                result = true;            }//result仍然为false且onTouchEvent返回为true的话            if (!result && onTouchEvent(event)) {                result = true;            }        }       ...        return result;    }

逻辑很清楚,就是一个判断一个判断来的,还是记住上面的处理流程就好,再复习一遍:
判断是否有可响应焦点→判断是否符合安全策略→是否为鼠标事件→是否注册onTouchListener并监听onTouch方法的返回值→监听onTouchEvent方法返回值。

那这里还有一点没有解决,就是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是否可点击        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;//判断是否设置为了不响应事件        if ((viewFlags & ENABLED_MASK) == DISABLED) {            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {                setPressed(false);            }            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;            // A disabled view that is clickable still consumes the touch            // events, it just doesn't respond to them.            //通过上面的注释也能看出,如果一个View是可点击的但是设置了不响应事件的话,则仍然会消费掉事件            return clickable;        }        //判断是否设置了代理        if (mTouchDelegate != null) {        //如果设置了代理则交由代理去消费事件            if (mTouchDelegate.onTouchEvent(event)) {                return true;            }        }...         return false;    }

至此……前期的判断结束了,我们开始核心的分发模块。

//需要判断是否可以点击或者(viewFlags & TOOLTIP) == TOOLTIP,那么这个TOOLTIP是个什么东西呢//代码我贴在下面        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {            ...            return true;        }

TOOLTIP:
官方文档,就类似于一小段悬停描述。
https://developer.android.google.cn/guide/topics/ui/tooltips?hl=en

    /**     * 

Indicates this view can display a tooltip on hover or long press.

* {@hide} * 这是上文TOOLTIP的值以及描述 */
static final int TOOLTIP = 0x40000000;

两者满足至少其一就会消费掉这个事件,中间代码逻辑其实很清晰,就是根据事件的类型去处理事件了,已经不是事件分发逻辑而是事件处理逻辑,大致框架拉一下:

            switch (action) {                case MotionEvent.ACTION_UP:...                    break;                case MotionEvent.ACTION_DOWN:                    ...                    break;                case MotionEvent.ACTION_CANCEL:                    ...                    break;                case MotionEvent.ACTION_MOVE:                    ...                    break;            }

更多相关文章

  1. android中获取view的位置
  2. Android(安卓)— 自定义圆形 ImageView 控件
  3. Android调用微信扫一扫
  4. android自由改变Dialog窗口位置的方法
  5. Android(安卓)开发facebook
  6. Android(安卓)JUnit单元测试基础实例
  7. android中保存Bitmap图片到指定文件夹中的方法
  8. 面试篇--android下网络通讯机制(三种网络通讯方式)
  9. Android使用Jsoup解析Html表格的方法

随机推荐

  1. 【Android(安卓)修炼手册】Gradle 篇 --
  2. Android清除缓存功能实现
  3. Android(安卓)自学之核心服务
  4. 关于CheckBox样式问题
  5. Android(安卓)Studio 实现实现学生信息的
  6. android 静默安装
  7. 【Android(安卓)进阶】Android(安卓)Home
  8. Android学习笔记35——ProgressBar进度控
  9. 早日下班小技巧,建议收藏哦!2020最新Androi
  10. Android(安卓)Visible属性学习