ViewGroup对触摸事件的分发响应过程

以前对于触摸事件的分发过程有过专门的研究,当时把郭林大神讲ViewGroup触摸分发的博客(http://blog.csdn.net/guolin_blog/article/details/9153747)看了不下3边,当时感觉对于分发已经了然于心。但是,后来在项目实战中发现好多一想不到的问题,郭大神的博客完全没法解释,凸(艹皿艹 ),然后自己打开ide,看ViewGroup源码发现跟郭大神博客里面的源码完全不一样,原来郭大神的博客说得是2.x版本ViewGroup关于触摸的分发的源码,无奈,相当于以前的研究都白费了。正好马上就进行技术分享会,我直接报,我要说触摸分发的过程。也借此机会,可以好好研究一下触摸的分发,并且解开我的疑惑。

触摸分发和响应的过程都是在dispatchTouchEvent方法中完成的,所以,此文主要分析ViewGroup中dispatchTouchEvent方法的实现。

想看懂ViewGroup中dispatchTouchEvent方法的代码,必须要先大致了解ViewGroup的一个内部类,TouchTarget类

/* Describes a touched view and the ids of the pointers that it has captured.
     *
     * This code assumes that pointer ids are always in the range 0..31 such that
     * it can use a bitfield to track which pointer ids are present.
     * As it happens, the lower layers of the input dispatch pipeline also use the
     * same trick so the assumption should be safe here...
     */
    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

        // The touched child view.
        public View child;

        // The combined bit mask of pointer ids for all pointers captured by the target.
        public int pointerIdBits;

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

        private TouchTarget() {
        }

        // 看到这个有没有很眼熟?是的Message里也有类似的实现,我们在之前介绍Message的文章里详细地分析过
        public static TouchTarget obtain(View child, int pointerIdBits) {
            final TouchTarget target;
            synchronized (sRecycleLock) {
                if (sRecycleBin == null) { // 没有可以回收的目标,则new一个返回
                    target = new TouchTarget(); 
                } else {
                    target = sRecycleBin; // 重用当前的sRecycleBin
                    sRecycleBin = target.next; // 更新sRecycleBin指向下一个
                     sRecycledCount--; // 重用了一个,可回收的减1
                    target.next = null; // 切断next指向
                }
            }
            target.child = child; // 找到合适的target后,赋值
            target.pointerIdBits = pointerIdBits;
            return target;
        }

        public void recycle() { // 基本是obtain的反向过程
            synchronized (sRecycleLock) {
                if (sRecycledCount < MAX_RECYCLED) {
                    next = sRecycleBin; // next指向旧的可回收的头
                    sRecycleBin = this; // update旧的头指向this,表示它自己现在是可回收的target(第一个)
                    sRecycledCount += 1; // 多了一个可回收的
                } else {
                    next = null; // 没有next了
                }
                child = null; // 清空child字段
            }
        }
    }
源码分析:

TouchTarget类用来记录触摸对象,包括此次触摸到的view(child成员变量)和触摸的id(pointerIdBits成员变量)。TouchTarget自身维护着一个TouchTarget对象形成的链表,obtain方法会从这个链表头上取出一个TouchTarget来使用,如果此时链表没有TouchTarget可用,则新造一个给调用端;recycle方法会将不用的TouchTarget对象重置后添加到链表的头部,供以后使用。当多点触摸的时候,会有多个TouchTarget,这些TouchTarget都是通过obtian方法获取的,获取的TouchTarget对象会形成一个触摸链,这个触摸链的起点是mFirstTouchTarget。本文重点说明触摸的分发响应过程,简单起见,以单点触摸的情况来说明,单点触摸中只会有一个TouchTarget对象。


下面我们来看ViewGroup中dispatchTouchEvent的实现:

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }

        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) { // view没有被遮罩,一般都成立
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // Handle an initial down.
            if (actionMasked == MotionEvent.ACTION_DOWN) { // 一堆touch事件(从按下到松手)中的第一个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(); // 作为新一轮的开始,reset所有相关的状态
            }

            // Check for interception.
            final boolean intercepted; // 检查是否要拦截
            if (actionMasked == MotionEvent.ACTION_DOWN // down事件
                    || mFirstTouchTarget != null) { // 或者之前的某次事件已经经由此ViewGroup派发给children后被处理掉了
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) { // 只有允许拦截才执行onInterceptTouchEvent方法
                    intercepted = onInterceptTouchEvent(ev); // 默认返回false,不拦截
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false; // 不允许拦截的话,直接设为false
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                // 在这种情况下,actionMasked != ACTION_DOWN && mFirstTouchTarget == null
                // 第一次的down事件没有被此ViewGroup的children处理掉(要么是它们自己不处理,要么是ViewGroup从一
                // 开始的down事件就开始拦截),则接下来的所有事件
                // 也没它们的份,即不处理down事件的话,那表示你对后面接下来的事件也不感兴趣
                intercepted = true; // 这种情况下设置ViewGroup拦截接下来的事件
            }

            // Check for cancelation.
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL; // 此touch事件是否取消了

            // Update list of touch targets for pointer down, if needed.
            // 是否拆分事件,3.0(包括)之后引入的,默认拆分
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            TouchTarget newTouchTarget = null; // 接下来ViewGroup判断要将此touch事件交给谁处理
            boolean alreadyDispatchedToNewTouchTarget = false;
            if (!canceled && !intercepted) { // 没取消也不拦截,即是个有效的touch事件
                if (actionMasked == MotionEvent.ACTION_DOWN // 第一个手指down
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) // 接下来的手指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 View[] children = mChildren;

                        final boolean customOrder = isChildrenDrawingOrderEnabled();
                        // 从最后一个向第一个找
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = customOrder ?
                                    getChildDrawingOrder(childrenCount, i) : i;
                            final View child = children[childIndex];
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                continue; // 不满足这2个条件直接跳过,看下一个child
                            }
                            
                            // child view能receive touch事件而且touch坐标也在view边界内

                            newTouchTarget = getTouchTarget(child);// 查找child对应的TouchTarget
                            if (newTouchTarget != null) { // 比如在同一个child上按下了多跟手指
                                // 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; // newTouchTarget已经有了,跳出for循环
                            }

                            resetCancelNextUpFlag(child);
                            // 将此事件交给child处理
                            // 有这种情况,一个手指按在了child1上,另一个手指按在了child2上,以此类推
                            // 这样TouchTarget的链就形成了
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                mLastTouchDownIndex = childIndex;
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                // 如果处理掉了的话,将此child添加到touch链的头部
                                // 注意这个方法内部会更新 mFirstTouchTarget
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true; // down或pointer_down事件已经被处理了
                                break; // 可以退出for循环了。。。
                            }
                        }
                    }

                    // 本次没找到newTouchTarget但之前的mFirstTouchTarget已经有了
                    if (newTouchTarget == null && mFirstTouchTarget != null) {
                        // Did not find a child to receive the event.
                        // Assign the pointer to the least recently added target.
                        newTouchTarget = mFirstTouchTarget;
                        while (newTouchTarget.next != null) {
                            newTouchTarget = newTouchTarget.next;
                        }
                        // while结束后,newTouchTarget指向了最初的TouchTarget
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }
            // 非down事件直接从这里开始处理,不会走上面的一大堆寻找TouchTarget的逻辑
            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                // 没有children处理则派发给自己处理
                // 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) { // 遍历TouchTarget形成的链表
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true; // 已经处理过的不再让其处理事件
                    } else {
                        // 取消child标记
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        // 如果ViewGroup从半路拦截了touch事件则给touch链上的child发送cancel事件
                        // 如果cancelChild为true的话
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true; // TouchTarget链中任意一个处理了则设置handled为true
                        }
                        if (cancelChild) { // 如果是cancelChild的话,则回收此target节点
                            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) {
                // 取消或up事件时resetTouchState
                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; // 返回处理的结果
    }

源码分析(以代码执行的顺序分析):

重要成员变量

    // First touch target in the linked list of touch targets.
    private TouchTarget mFirstTouchTarget;//ViewGroup的child消耗touch,此变量记录下消耗touch的view

    protected int mGroupFlags;//各种flag值的集合,其中包含FLAG_DISALLOW_INTERCEPT

1.触摸以MotionEvent.ACTION_DOWN为开始,在触摸的开始,ViewGroup会清除所有的关于上次触摸的信息。

8行一般为true,13行判断如果是ACTION_DOWN,则调用cancelAndClearTouchTargets()和resetTouchState(),在这两个方法中分别mFirstTouchTarget=null和重置mGroupFlags的FLAG_DISALLOW_INTERCEPT标志位。

代码如下:

    /**
     * Cancels and clears all touch targets.
     */
    private void cancelAndClearTouchTargets(MotionEvent event) {
        if (mFirstTouchTarget != null) {
            boolean syntheticEvent = false;
            if (event == null) {
                final long now = SystemClock.uptimeMillis();
                event = MotionEvent.obtain(now, now,
                        MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
                event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
                syntheticEvent = true;
            }

            for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
                resetCancelNextUpFlag(target.child);
                dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
            }
            clearTouchTargets();

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

    /**
     * Resets the cancel next up flag.
     * Returns true if the flag was previously set.
     */
    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;
    }

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

5行如果mFirsetTouchTarget!=null,会走到19行调用clearTouchTargets(),clearTouchTargets()中将mFirstTouchTarget为起点的链表都清空,最终将mFirstTouchTarget=null.

    /**
     * Resets all touch state in preparation for a new cycle.
     */
    private void resetTouchState() {
        clearTouchTargets();
        resetCancelNextUpFlag(this);
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;//重置mGroupFlags中FLAG_DISALLOW_INTERCEPT标志位
        mNestedScrollAxes = SCROLL_AXIS_NONE;
    }
FLAG_DISALLOW_INTERCEPT标志位,决定了ViewGroup是否允许拦截touch事件,往往通过child.getParent().requestDisallowInterceptTouchEvent(true)方法来设置,当传入true的话,child的所有父控件都不会拦截touch事件,这样保证了child肯定会收到此touch事件。

requestDisallowInterceptTouchEvent代码如下:

    /**
     * {@inheritDoc}
     */
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

        if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
            // We're already in this state, assume our ancestors are too
            return;
        }

        if (disallowIntercept) {
            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
        } else {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }

        // Pass it up to our parent
        if (mParent != null) {
            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
        }
    }

tip:我们自己做项目时经常会重写child的onTouchEvent方法,判断当情况A时调用child.getParent().requestDisallowInterceptTouchEvent(true),来让父控件不去拦截touch事件,当情况B时,调用child.getParent().requestDisallowInterceptTouchEvent(false),运许父控件拦截touch事件,如果此时父控件也需要拦截touch,那么父控件会拦截touch,child再无法收到touch事件,child的onTouchEvent方法也就再得不到执行。但是,当用户松开手机再按下,产生下一个触摸事件时,child的onTouchEvent又得到执行,就是因为父控件ViewGroup在dispatchTouchEvent方法中判断MotionEvent的action为ACTION_DOWN,重置了mGroupFlags标志位所致。


2.ViewGroup通过MotionEvent的action,mFirstTouchTarget,mGroupFlags中FLAG_DISALLOW_INTERCEPT,三个值共同判断是否要拦截此touch。

(不拦截touch,ViewGroup会根据触摸在哪个view上,将touch逐层分发到那个view去,即逐层调用view的dispatchTouchEvent方法,如果拦截touch,则不会向下分发,直接调用ViewGroup的onTouchEvent方法,之后便结束了此ViewGroup的dispatchTouchEvent方法)

dispatchTouchEvent代码片段:

22到40行代码判断ViewGroup是否拦截touch事件

mFirstTouchTarget记录的是ViewGroup中消耗了touch事件的child的引用;

当一个新的触摸事件到来,由于之前重置了ViewGroup中记录的上一个touch的信息,此时MotionEvent的action为ACTION_DOWN并且mFirstTouchTarget==null,mGroupFlags中FLAG_DISALLOW_INTERCEPT为false,所以,进入27行代码,即此处触摸是否拦截又ViewGroup的onInterceptTouch方法决定。

当MotionEvent为非ACTION_DOWN时,如果mFirstTouchEvent!=null,即ViewGroup中被触摸到的子View消耗了此touch事件,此时是否拦截touch由mGroupFlags中FLAG_DISALLOW_INTERCEPT标志位值和onInterceptTouch方法返回值共同决定。

当MotionEvent为非ACTION_DOWN时,如果mFirstTouchEvent==null,即ViewGroup中被触摸到的子View没有消耗此touch事件,进入39行,ViewGroup拦截touch事件。

tips:当我们自定义的View重写onTouchEvent方法,在onTouchEvent方法的ACITON_DOWN中返回false,此View便再无法收到其后的touch事件,就是因为ViewGroup的子View没有消耗touch事件,mFirstTouchTarget==null,intercept=true,ViewGroup便会拦截其后的touch事件。


3.根据intercept的值,决定是否拦截touch事件,不拦截将touch传递到触摸到的View上去,当分发到的View消耗了此touch,mFirstTouchTarget会记下此View;分发到的View没有消耗此touch,mFirstTouchTarget一直为null。

51行if(!cancel&&!intercepted),默认cancel为false,当intercept为false,即不拦截时,代码会走到64行,childrenCount !=0,即ViewGroup有child,会走到73行,从后往前遍历ViewGroup的所有child,77,78行判断是否触摸在此child上,并且此child接受触摸事件。

tips:我们经常使用FrameLayout来做重叠的布局,当我们的触摸事件落在多个重叠view上时,这时候如果前边的view消耗了事件,后边的view就收不到触摸事件了;就是因为ViewGroup在分发touch的时候,是从后往前遍历的(view重叠时,就是从上向下),如果上面的view消耗了touch,后边的view自然就接收不到touch了。

84-92行主要是对多点触控进行处理,暂时不管,代码进入关键的96行,调用dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign),此方法将dispatchTouchEvent方法中传入的MotionEvent的相对于ViewGroup的x,y坐标转化为相对于child的x,y坐标,然后,分发给此child。

dispatchTransformedTouchEvent代码:

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

13行,如果传入的cancel==true或者event.getAction()==MotionEvent.ACTION_CANCEL,把event的action设置为ACTION_CANCEL。

15行,如果child==null,super.dispatchTouchEvent();否则,child.dispatchTouchEvent();

super.dispatchTouchEvent()就是调用view的dispatchTouchEvent()方法,view的dispatchTouchEvent实现,其实就是调用自身的onTouchEvent(),即child==null,调用ViewGroup的onTouchEvent(),child!=null,将touch分发给传入的child。

20行,恢复event的action为原来的action;

38-57行,60-71行也是类似逻辑,如果child==null,super.dispatchTouchEvent();否则,child.dispatchTouchEvent();

由dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)调用参数可知,最终执行了child.dispatchTouchEvent()将触摸事件分发给了子view。

下面为View的dispatchTouchEvent源码

    /**
     * Pass the touch screen motion event down to the target view, or this
     * view if it is the target.
     *
     * @param event The motion event to be dispatched.
     * @return True if the event was handled by the view, false otherwise.
     */
    public boolean dispatchTouchEvent(MotionEvent event) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        if (onFilterTouchEventForSecurity(event)) { // 一般都成立
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) { // 先在ENABLED状态下尝试调用onTouch方法
                return true; // 如果被onTouch处理了,则直接返回true
            }
            // 从这里我们可以看出,当你既设置了OnTouchListener又设置了OnClickListener,那么当前者返回true的时候,
            // onTouchEvent没机会被调用,当然你的OnClickListener也就不会被触发;另外还有个区别就是onTouch里可以
            // 收到每次touch事件,而onClickListener只是在up事件到来时触发。
            if (onTouchEvent(event)) {
                return true;
            }
        }

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        return false; // 上面的都没处理,则返回false
    }

View的dispatchTouchEvent方法很简单,16-17行,就是判断有没有设置OnTouchListener,如果设置了,并且OnTouchListener.onTouch()返回了true,则dispatchTouchEvent直接返回true;如果没有设置OnTouchListener或者OnTouchListener.onTouch()返回false,则到23行,调用onTouchEvent(),将onTouchEvent()的返回值,作为dispatchTouchEvent的返回值。

继续说ViewGroup的dispatchTouchEvent过程,如果child消耗了此touch,则会进入96行的if语句块中,最终执行到第104行,调用addTouchTarget()方法,那么在此方法中都做了什么呢,我们来看看其源码:

    /**
     * Adds a touch target for specified child to the beginning of the list.
     * Assumes the target child is not already present.
     */
    private TouchTarget addTouchTarget(View child, int pointerIdBits) {
        TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }
看以上代码,我们知道此方法就是在拿消耗掉触摸的child生成一个TouchTarget,添加到touch链的头部,并且mFirstTouchTarget指向此TouchTarget。主要就是更新mFirstTouchTarget的引用指向,消耗了此touch的child。

继续分析ViewGroup的dispatchTouchEvent方法

前面所有的判断机会都要求action为ACTION_DOWN,126行后终于没有了这条判断,也就是处理所有action。

126行,如果此ViewGroup的child没有处理此touch,mFirstTouchTarget==null,调用dispatchTransformedTouchEvent(ev, canceled, null, idBitsToAssign); 由上面分析dispatchTransformedTouchEvent方法代码可知,第三个参数child为null,会调用super.dispatchTouchEvent(),即ViewGroup的onTouchEvent。

也就是如果ViewGroup的child不消耗此touch,则调用ViewGroup的onTouchEvent方法。

134行后代码,则代表ViewGroup中有child消耗了此touch,135行将mFirstTouchTarget赋值给target,因为进入此else语句mFirstTouchTarget肯定不为空,所以target不为空,进入136行while循环,138行的if判断,对应于上边96行的if括号内的代码,如果这个touch第一次进行分发,走入了96行的if括号,也就是此touch的ACTION_DOWN被子view消耗后,代码走到138行,直接handled=true,不再处理。如果不是第一次分发,49行newTouchTarget=null,MotionEvent的action不是ACTION_DOWN,不会进入51,52行if语句块;所以这里会走到else语句。142行就是判断是否取消的操作,或者本来没有拦截现在变为了拦截。这里我们不讨论取消的情况,只考虑拦截和不拦截的情况,即cancelChild的值去intercepted的值。如果不拦截,cancelChild==false,调用dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits),会将此touch直接分发给mFirstTouchTarget记录的child;如果拦截(ViewGroup由原来不拦截件变为拦截),cancelChild==true,调用dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits),会将ACTION_CANCEL分发给mFirstTouchTarget记录的child。

150行,如果cancelChild==true,predecessor会一直为null,因为158行有continue语句,161行predecessor=target这句永远得不到执行,最终会把mFirstTouchTarget置为null,这样会导致23行的if语句块再得不到执行,及onInterceptTouchEvent方法再得不到执行。如果cancelChild==false,则mFirstTouchTarget会保存不变,只是把此touch分发到mFirstTouchTarget记录的child上去。

tips:如果ViewGroup的子View消耗了touch的ACTION_DOWN,此touch的后续事件,不再重新分发,直接传递到消耗ACTION_DOWN的View上去。
 如果ViewGroup一开始不拦截,在后续事件中变为拦截(在onInterceptTouchEvent方法中根据滑动动作,返回了true),原来消耗touch的子View会受到ACTION_CANCEL事件,并且ViewGroup的onInterceptTouchEvent方法在此次touch事件中再不会执行。


nnd,终于写完了,写了好几天,语言不太会组织,程序员通病吧,单点触控只有一个TouchTarget,多点触控按下几个手指这几个手指触点会形成一个TouchTarget的链,简单起见,多点触控就没讲,单点和多点事件分发处理的过程是完全一样的的。大伙看思路就好。如有理解不一致的地方,还望在评论区留言讨论。






  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值