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

来源:互联网 发布:网络直销模式典型公司 编辑:程序博客网 时间:2024/06/16 19:52

以前对于触摸事件的分发过程有过专门的研究,当时把郭林大神讲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的链,简单起见,多点触控就没讲,单点和多点事件分发处理的过程是完全一样的的。大伙看思路就好。如有理解不一致的地方,还望在评论区留言讨论。






1 0