Android-事件分发机制源码攻略

2017/11/2 posted in  Android

前言

android事件分发算是自定义View不可缺失的一部分,事件分发是指那一类跟屏幕交互的操作等事件,例如滑动,点击,长按这类。这些事件都是由摁下、移动、抬起等基本事件组成的。那事件分发是指当你点击了屏幕,这个事件是如何从Activity传递到真正处理这个事件的View上的过程。例如,我们在做ListView跟ViewPager嵌套的时候,既能左右滑动,又能上下滑动,这些事件是如何避免彼此间的冲突的。下面我会分三节来介绍这一原理。

首先,这次的源码分析是基于25.0.3版本进行的。

分发事件

分发的事件主要是MotionEvent这个类所表示的点击、移动、抬起、取消等事件

MotionEvent.ACTION_DOWN

MotionEvent.ACTION_MOVE

MotionEvent.ACTION_UP

MotionEvent.ACTION_CANCEL

分发的对象

分发的对象是指收到上述事件的类

Activity

ViewGroup

View

上述三个类是主要的事件分发对象,后期的讨论也是集中在这三个类里面。这里有个点得提一下就是ViewGroup是View的子类。这些事件会在这三者的dispatchTouchEvent、onTouchEvent这两个方法里面传递,还有一个ViewGroup特有的onInterceptTouchEvent方法。下面给出以上提到的类以及方法之间的关系。

20171112151041696377769.png
20171112151041696377769.png

从这张图,我们可以看出,不同返回值,事件的分发方向不同,这里不做详细分析,这张图是否画得出来作为你对这次源码阅读的成绩。

Demo

Activity

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        Log.i(TAG,"dispatchTouchEvent"+ev.getAction());

        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(TAG,"onTouchEvent"+event.getAction());
        return super.onTouchEvent(event);
    }

CusViewGroup

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        Log.i(TAG,"dispatchTouchEvent"+ev.getAction());

        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {

        Log.i(TAG,"onInterceptTouchEvent"+ev.getAction());

        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        Log.i(TAG,"onTouchEvent"+event.getAction());

        return super.onTouchEvent(event);
    }

View

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        Log.i(TAG,"dispatchTouchEvent"+ev.getAction());

        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        Log.i(TAG,"onTouchEvent"+event.getAction());

        return super.onTouchEvent(event);
    }

布局

<?xml version="1.0" encoding="utf-8"?>
<com.example.coffeetime.cusviewdemo.CusLineaLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.coffeetime.cusviewdemo.MainActivity">

    <com.example.coffeetime.cusviewdemo.CusView
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:background="@color/colorPrimary"/>

</com.example.coffeetime.cusviewdemo.CusLineaLayout>

结果

20171112151041703651591.png
20171112151041703651591.png

ACTION_DOWN的值为0
ACTION_MOVE的值为1

从输出的结果的第一行可以看出来最先获取事件的是Activity这一层,从倒数第三行可以看出,ACTION_DOWN这个事件最终消费是在Activity的onTouchEvent这个方法被消费。这个结果跟上面的图片是一致的。

再看看输出结果的倒数两行,ACTION_MOVE事件从dispatchTouchEvent直接就传给了同级的onTouchEvent方法去了,说明ACTION_DOWN事件在哪里被消费了,后续事件也在那里消费,并且跳过中间传递。这个原因会在分析源码的时候给出解释。

Activity

从上述的结果,我们可以得出事件最先被拦截的地方是从Activity的dispatchTouchEvent方法开始,那我们的源码攻略也从这里开始。

 /**
     * Called to process touch screen events.  You can override this to
     * intercept all touch screen events before they are dispatched to the
     * window.  Be sure to call this implementation for touch screen events
     * that should be handled normally.
     *
     * @param ev The touch screen event.
     *
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

这个方法首先判断事件类型,如果是ACTION_DOWN事件,则先执行onUserInteraction()方法;

public void onUserInteraction() {
}

这个方法没有实现,根据文档注释,当有任意一个按键、触屏或者轨迹球事件发生时,栈顶Activity的onUserInteraction会被触发。如果我们需要知道用户是不是正在和设备交互,可以在子类中重写这个方法,去获取通知(比如取消屏保这个场景)。跟这个方法配对的还有onUserLeaveHint方法,这个方法是在用户离开设备的时候触发的。

我们接着回到刚刚那个地方,判断完事件ACTION_DOWN事件之后,会执行getWindow().superDispatchTouchEvent(ev)这个方法,这个getWindow获取的是哪个Window呢,我们进去看看。

public Window getWindow() {
    return mWindow;
}
/**
 * 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.
 *
 * <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 */
public abstract class Window {}

通过这块注释,我们可以看出,window的实现类是phoneWindow。其实在Android里面很多都是可以通过阅读注释或者是官方文档去找出具体的实现类,不是只能依靠百度;好了,那我们可以直接进到phoneWindow类去看下superDispatchTouchEvent这个方法做了些什么

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

这个方法又是调用mDecor的同名方法去实现,mDecor又是什么

phoneWindow

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

DecorView

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

DecorView 是window的顶级View,而DecorView是继承FrameLayout的布局,接着去FragmeLayout里面寻找dispatchTouchEvent方法,而FragmeLayout并没有实现这个方法,也即这个是直接交给ViewGroup去处理的。

小结

在Activity层事件的传递过程如下图

20171112151041706262125.png
20171112151041706262125.png

从Activity的dispatchTouchEvent获取事件经过PhoneWindow、DecorView再到最终的ViewGroup,这一层代码比较简单,没有比较难分析的,只是经过的类比较多,其实只要把握的主线,分析起来还是比较简单的。这一篇比较少,下一篇是ViewGroup的分析,由于ViewGroup的源码比较多,所以才拆开来写,不然太长了。

ViewGroup

如果是ACTION_DOWN事件,就会去寻找子View来处理,如果找不到子View来处理,就自己处理。

如果不是ACTION_DOWN事件,就会把这个事件传给处理了ACTION_DOWN事件的View来处理。

大致就这两个逻辑,虽说比较粗略,不过,这对于接下来看源码就足够了,并且源码有比较多的注释,基本上大致的方向是可以弄懂了。

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        ... 
        //返回值的关键,注意留意handled的值发生改变的地方
        boolean handled = false;
        //判断当前window是否有被遮挡,true为分发这个事件,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
                // due to an app switch, ANR, or some other state change.
//在新的事件开始(即是新的ACTION_DOWN事件),需要清除掉之前的状态以及设置mFirstTouchTarget=null;
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            // Check for interception.
            final boolean intercepted;
            //子View唯一一个可以用来控制父类事件传递
            //只有ACTION_DOWN事件跟mFirstTouchTarget不为空的情况,后面的讨论大多是围绕着mFirstTouchTarget来进行的
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                //是否拦截事件,disallowIntercept为true是不拦截,false是拦截
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    //一般重写onInterceptTouchEvent方法
                    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.
            //split是否分发给多个子View,默认为false
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
            //如果不被拦截即可进入或者不是ACTION_CANCEL事件
            if (!canceled && !intercepted) {

                // If the event is targeting accessiiblity focus we give it to the
                // view that has accessibility focus and if it does not handle it
                // we clear the flag and dispatch the event to all children as usual.
                // We are looking up the accessibility focused host to avoid keeping
                // state since these events are very rare.
                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                        ? findChildWithAccessibilityFocus() : null;
                //只有ACTION_DOWN等事件能够进入
                if (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.
                        //获取按Z轴从大到小排序的子View列表
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        //是否有自定义顺序,一般为false
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            //确认这个子View的下标
                            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;
                            }
                            //是否获得可见,并且落在child的布局范围内
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }
                            //Child是否已经处理过事件了,有的话更改pointerIdBits值,并结束查找
                            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);
                            //分发给View的dispatchTouchEvent
                            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赋值,该事件已经被子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();
                    }

                    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.
            // 没有子View处理,则自己处理
            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.
                //处理除了ACTION_DOWN以外的事件
                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;
                        //如果这个事件被拦截了,intercepted为true
                        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;
    }

像这么长的代码,很多地方是可以跳过的,不过仔仔细细分析,特别是像Google出品的(个人愚见),因为这些东西考虑的方方面面比较多,而我们这个只是为了了解事件的分发,绘制那块我们不会过多涉及。(说跑题了)回到正题来,像这么长的代码,之前学习的时候,有个牛人是这么写的(个人总结)。

从结果出发,留意改变的结果的地方

上面的dispatchTouchEvent返回值是由handle决定,我们先来看第一处第8行代码

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

这个onFilterTouchEventForSecurity方法如果返回false的话,基本上里面的代码都不用分析了,直接返回false。那我们进去看看这个方法做了什么。

    /**
     * Filter the touch event to apply security policies.
     *
     * @param event The motion event to be filtered.
     * @return True if the event should be dispatched, false if the event should be dropped.
     *
     * @see #getFilterTouchesWhenObscured
     */
    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;
    }

这是一个安全策略方面的过滤,我们来看下这两个变量FILTER_TOUCHES_WHEN_OBSCURED、MotionEvent.FLAG_WINDOW_IS_OBSCURED是什么意思

    /**
     * Indicates that the view should filter touches when its window is obscured.
     * Refer to the class comments for more information about this security feature.
     * {@hide}
     */
    static final int FILTER_TOUCHES_WHEN_OBSCURED = 0x00000400;
    /**
     * This flag indicates that the window that received this motion event is partly
     * or wholly obscured by another visible window above it.  This flag is set to true
     * even if the event did not directly pass through the obscured area.
     * A security sensitive application can check this flag to identify situations in which
     * a malicious application may have covered up part of its content for the purpose
     * of misleading the user or hijacking touches.  An appropriate response might be
     * to drop the suspect touches or to take additional precautions to confirm the user's
     * actual intent.
     */
    public static final int FLAG_WINDOW_IS_OBSCURED = 0x1;

从上面的代码注释可以看出来,这个View不能被其他的window遮挡住,这是谷歌的一个安全策略,避免被恶意程序误导用户或劫持触摸。

第二处handle的改变是在172行

        if (mFirstTouchTarget == null) {
               // No touch targets so treat this as an ordinary view.
               handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
        ...
        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;
                    }
                ...

很明显handled的值又跟mFirstTouchTarget、alreadyDispatchedToNewTouchTarget这两个值有关,另外还跟dispatchTransformedTouchEvent()这个方法有关,dispatchTransformedTouchEvent()方法,我们留在后面分析,我们先来看看这两个值是在什么时候在哪里被改变的。

         mLastTouchDownX = ev.getX();
         mLastTouchDownY = ev.getY();
         //给mFirstTouchTarget赋值,该事件已经被子View确认处理了
         newTouchTarget = addTouchTarget(child, idBitsToAssign);
         alreadyDispatchedToNewTouchTarget = true;

这个是第145行的代码,这里是找到处理事件的子View后,做的赋值,addTouchTarget这个方法里面会对
mFirstTouchTarget赋值。

好了,如果是这样,我们再从上面的第13行开始分析。

 // 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
                // due to an app switch, ANR, or some other state change.
//在新的事件开始(即是新的ACTION_DOWN事件),需要清除掉之前的状态以及设置mFirstTouchTarget=null;
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

这里先对该事件进行判断,如果是ACTION_DOWN事件会进到这个方法里面,做一些处理。我们来看下这两个方法都做了哪些。

    /**
     * Cancels and clears all touch targets.
     */
    private void cancelAndClearTouchTargets(MotionEvent event) {
        if (mFirstTouchTarget != null) {
            boolean syntheticEvent = false;
            //假如event为null,重新实例一个取消(MotionEvent)的事件
            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);
            }
           //重置mFirstTouchTarget
            clearTouchTargets();

            if (syntheticEvent) {
                event.recycle();
            }
        }
    }

从这个方法的名字可以看出来,这个方法做了两件事取消跟清除TouchTarget,首先是取消,这里的取消是指分发ACTION_CANCEL事件,在我上面注释代码的第18行,dispatchTransformedTouchEvent()这个方法的第二个参数为true,这个值会在更改事件为ACTION_CANCEL,并分发给上次处理事件的View。这个分发事件的方法,我们留在后面分析,现在继续分析清除。

    /**
     * Clears all touch targets.
     */
    private void clearTouchTargets() {
        TouchTarget target = mFirstTouchTarget;
        if (target != null) {
            do {
                TouchTarget next = target.next;
                target.recycle();
                target = next;
            } while (target != null);
            mFirstTouchTarget = null;
        }
    }

这个方法很简单了,就对TouchTarget的next是回收,最后再把mFirstTouchTarget置null。好了,这两个方法分析完,我们再回到刚刚的那个地方,看到还有一个方法resetTouchState()

    /**
     * Resets all touch state in preparation for a new cycle.
     */
    private void resetTouchState() {
        clearTouchTargets();
        resetCancelNextUpFlag(this);
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        mNestedScrollAxes = SCROLL_AXIS_NONE;
    }

这个方法除了clearTouchTargets()、resetCancelNextUpFlag()这两个方法外,还对 mGroupFlags 这个标志做一个拦截方面的修改,这个标志可以让子View请求父布局不要去拦截某个事件(ACTION_DOWN除外),并且可通过getParent().requestDisallowInterceptTouchEvent()去修改这个值。

  // Check for interception.
            final boolean intercepted;
            //子View唯一一个可以用来控制父类事件传递
            //只有ACTION_DOWN事件跟mFirstTouchTarget不为空的情况,后面的讨论大多是围绕着mFirstTouchTarget来进行的
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                //是否拦截事件,disallowIntercept为true是不拦截,false是拦截
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    //一般重写onInterceptTouchEvent方法
                    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;
            }

这里有个判断,只有ACTION_DOWN以及mFirstTouchTarget不为的空的情况下,才允许进入。我们来先说下,什么时候mFirstTouchTarget会不为空,我这边先简单说下,后面代码会提及;mFirstTouchTarget是在这个事件被所在的子View消费了,这个值才不会空,即使是本身ViewGroup消费了,这个值也是为空。按照这个思路的话,大家估计也不难理解我上面说的子View可以请求父布局对ACTION_DOWN以外的事件不做拦截,另外还有一点就是,一般重写只针对onInterceptTouchEvent这个方法,而dispatchTouchEvent这个方法倒是很少重写。像我们经常遇到的ViewPager跟ScrollView这个横竖滑动冲突的问题,你们去看这两个控件源码,就可以看到都是重写了onInterceptTouchEvent这个方法。

我们回到我上面提供的源码注解中,执行上述判断后,如果canceled跟intercepted都为false的话,并且这个事件为ACTION_DOWN事件,接下来将寻找满足消费条件的子View。我们来看下,是按照什么顺序来寻找View的。

按照我上面提供源码走下来,在87行处有着下面这个方法,这个方法主要是将子View按照Z轴的大小排序。

  ArrayList<View> buildOrderedChildList() {
        final int childrenCount = mChildrenCount;
        if (childrenCount <= 1 || !hasChildWithZ()) return null;

        if (mPreSortedChildren == null) {
            mPreSortedChildren = new ArrayList<>(childrenCount);
        } else {
            // callers should clear, so clear shouldn't be necessary, but for safety...
            mPreSortedChildren.clear();
            mPreSortedChildren.ensureCapacity(childrenCount);
        }

        //自定义View排序
        final boolean customOrder = isChildrenDrawingOrderEnabled();
        for (int i = 0; i < childrenCount; i++) {
            // add next child (in child order) to end of list
            final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
            final View nextChild = mChildren[childIndex];
            final float currentZ = nextChild.getZ();

            // insert ahead of any Views with greater Z
            int insertIndex = i;
           //有点类似于插入排序,按Z轴从小到大排序
            while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {
                insertIndex--;
            }
            mPreSortedChildren.add(insertIndex, nextChild);
        }
        return mPreSortedChildren;
    }

其中getAndVerifyPreorderedIndex只是对View的下标进行再次确定。这里面提到一个自定义排序的问题,正常情况的布局排序是根据xml的顺序或者addView的顺序决定的。当然google也提供了setChildrenDrawingOrderEnabled(),getChildDrawingOrder()这两个方法进行自定义排序,有需求的可以去自行了解下,我们就不深入探讨了。

  if (childWithAccessibilityFocus != null) {
        if (childWithAccessibilityFocus != child) {
              continue;
        }
        childWithAccessibilityFocus = null;
        i = childrenCount - 1;
  }

现在是取到了所有的子View,那么接下来就是筛选哪些View可以处理了。首先是先获取到哪个是获取焦点的View,并且这个View是否在这些子View里面。如果找到了就走到下一步。

//判断这个View是否具备处理的条件
if (!canViewReceivePointerEvents(child)
        || !isTransformedTouchPointInView(x, y, child, null)) {
    ev.setTargetAccessibilityFocus(false);
    continue;
}

我们来看看第一个判断方法

  /**
     * Returns true if a child view can receive pointer events.
     * @hide
     */
    private static boolean canViewReceivePointerEvents(@NonNull View child) {
        return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                || child.getAnimation() != null;
    }

第二个方法

/**
 * Returns true if a child view contains the specified point when transformed
 * into its coordinate space.
 * Child must not be null.
 * @hide
 */
protected boolean isTransformedTouchPointInView(float x, float y, View child,
        PointF outLocalPoint) {
    final float[] point = getTempPoint();
    point[0] = x;
    point[1] = y;
    transformPointToViewLocal(point, child);
    final boolean isInView = child.pointInView(point[0], point[1]);
    if (isInView && outLocalPoint != null) {
        outLocalPoint.set(point[0], point[1]);
    }
    return isInView;
}

可见或者是正在执行动画的,并且位置是落在这个View的范围的。满足这些条件外,再判断这个View是否已经是在mFirstTouchTarget的子View里面了,如果是的话,也是结束循环了。

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

以上条件都满足的话,我们就进行分发事件的方法,我们来看下这个方法做了什么操作。

/**
     * Transforms a motion event into the coordinate space of a particular child view,
     * filters out irrelevant pointer ids, and overrides its action if necessary.
     * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
     */
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        // Canceling motions is a special case.  We don't need to perform any transformations
        // or filtering.  The important part is the action, not the contents.
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }

        // Calculate the number of pointers to deliver.
        final int oldPointerIdBits = event.getPointerIdBits();
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

        // If for some reason we ended up in an inconsistent state where it looks like we
        // might produce a motion event with no pointers in it, then drop the event.
        if (newPointerIdBits == 0) {
            return false;
        }

        // If the number of pointers is the same and we don't need to perform any fancy
        // irreversible transformations, then we can reuse the motion event for this
        // dispatch as long as we are careful to revert any changes we make.
        // Otherwise we need to make a copy.
        final MotionEvent transformedEvent;
        if (newPointerIdBits == oldPointerIdBits) {
            if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    event.offsetLocation(offsetX, offsetY);

                    handled = child.dispatchTouchEvent(event);

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

        // Perform any necessary transformations and dispatch.
        if (child == null) {
            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;
    }

这个方法一看就有点长了,慌不慌~其实这个方法就做了两件事,第一件事,就是如果cancel为true的话,更改这个事件为ACTION_CANCEL;第二件事,就是child为null的话,调用super.dispatchTouchEvent(event);child不为空的话,就调用super.dispatchTouchEvent(event);好吧,其实这个方法,只需要看上面那部分就差不多了。

...
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
...

精简版的分发~~~

如果dispatchTransformedTouchEvent方法返回true的话,就代表了这个事件已经被子View消费了,接下来关键的方法就是调用addTouchTarget()这个方法,给mFirstTouchTarget赋值。

 /**
     * Adds a touch target for specified child to the beginning of the list.
     * Assumes the target child is not already present.
     */
    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }

如果dispatchTransformedTouchEvent方法返回false的话,那么就代表这个事件没有View消费,那就是只能自己消费了

 if (mFirstTouchTarget == null) {
           // No touch targets so treat this as an ordinary view.
           handled = dispatchTransformedTouchEvent(ev, canceled, null,
                   ouchTarget.ALL_POINTER_IDS);
 }

其实到这里,整个ACTION_DOWN事件的传递就结束了。我们来做了小结,当有触摸事件传递过来时

  1. 先对当前设备状态进行判断,是否没被遮挡

  2. 紧接着如果是ACTION_DOWN事件的话,就清除状态

  3. 如果onInterceptTouchEvent返回true,则事件交给自己处理

  4. 如果是ACTION_DOWN事件的话,先去寻找获得焦点的View,如果找到了,就分发给View去处理;如果找不到就交给自己处理。

接着我们再来说下除了ACTION_DOWN以外的事件传递情况,从上面的demo我们可以得知,消费了ACTION_DOWN事件,后续的事件也将给这个View消费。也即是mFirstTouchTarget != null的情况。

// Dispatch to touch targets, excluding the new touch target if we already
                // dispatched to it.  Cancel touch targets if necessary.
                //处理除了ACTION_DOWN以外的事件
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                   //alreadyDispatchedToNewTouchTarget为true的话,说明已经被消费了
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        //如果这个事件被拦截了,intercepted为true
                        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;
                }
            }

上面代码的第9行,这一块的的判断我们可以回溯到之前的mFirstTouchTarget赋值,也即是addTouchTarget()这个方法。可以发现,上述的判断如果为true,说明这个事件已经被消费了,所以handled就为true了。

上面代码的第12行,如果intercepted为true的话,那cancelChild也就为true了。而dispatchTransformedTouchEvent()上面已经分析过,cancelChild为true,会向之前消费事件的View发送ACTION_CANCEL事件。后面再把mFirstTouchTarget置成next,也即是null,那么接下来的事件将被本身给消费掉。这也验证了我们上面的demo。当然,大家也可以多做几个例子好好理解理解。

下面是整个dispatchTouchEvent()里面关键方法的调用流程,可以方便理解。

20171112151041743495516.png
20171112151041743495516.png

好了,整个ViewGroup层dispatchTouchEvent传递到View层的dispatchTouchEvent或者传递给super.dispatchTouchEvent(event),下一节将对View层的源码进行解析。