Android View事件分发机制

两个流程

  1. 向下分发
    向下分发:Android接收到触屏事件,由Activity开始逐层向下传递,直到传递给一个View,或者ViewGroup拦截这个事件为止。

    分发和传递的方法分别为dispatchTouchEvent,onInterceptTouchEvent
    其中dispatchTouchEvent,View、Activity、ViewGroup都有
    onInterceptTouchEvent 只有ViewGroup有

    事件传递给ViewGroup时候,ViewGroup会调用dispatchTouchEvent,
    dispatchTouchEvent又会调用onInterceptTouchEvent方法
    如果onInterceptTouchEvent返回true(拦截)那么,这个事件就交给这个ViewGroup来消耗,如果不拦截,向下找子View看哪个消耗(调用onTouchEvent)

    注意:onInterceptTouchEvent 不是一直调用的
    触屏事件可以是一些列事件 dowm->move->move…move->up
    如果这个事件是由这个ViewGroup处理的那么在之后不会调用onInterceptTouchEvent
    如果有个标志位FLAG_DISALLOW_INTERCEPT 设置为true 也不会再次调用
    onInterceptTouchEvent 而是直接把其赋值为false

  2. 向上传递
    当一个事件传到最底层的View的时候会调用onTouchEvent,如果这个onTouchEvent返回false,就交给上层处理,一直往上直到Activity

注意:
onTouchListener的onTouch方法优先级比onTouchEvent高,会先触发。

假如onTouch方法返回false会接着触发onTouchEvent,反之onTouchEvent方法不会被调用。

内置诸如click事件的实现等等都基于onTouchEvent,假如onTouch返回true,这些事件将不会被触发。

public boolean dispatchTouchEvent(MotionEvent event){  
    ... ...  
    if(onFilterTouchEventForSecurity(event)){  
        ListenerInfo li = mListenerInfo;  
        if(li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED  
            && li.mOnTouchListener.onTouch(this, event)) {  
            return true;  
        }  
        if(onTouchEvent(event)){  
            return true;  
        }  
    }  
    ... ...  
    return false;  
}  

dispatchTouchEvent分析

引自:http://blog.csdn.net/u011637339/article/details/50427156

import android.view.MotionEvent;    
import android.view.View;    

public class ZeroDispatchTouchEvent {    
    /**  
     * dispatchTouchEvent()源码学习及其注释  
     * 常说事件传递中的流程是:dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent  
     * 在这个链条中dispatchTouchEvent()是处在链首的位置当然也是最重要的.  
     * 在dispatchTouchEvent()决定了Touch事件是由自己的onTouchEvent()处理  
     * 还是分发给子View处理让子View调用其自身的dispatchTouchEvent()处理.  
     *   
     *   
     * 其实dispatchTouchEvent()和onInterceptTouchEvent()以及onTouchEvent()的关系  
     * 在dispatchTouchEvent()方法的源码中体现得很明显.  
     * 比如dispatchTouchEvent()会调用onInterceptTouchEvent()来判断是否要拦截.  
     * 比如dispatchTouchEvent()会调用dispatchTransformedTouchEvent()方法且在该方法中递归调用  
     * dispatchTouchEvent();从而会在dispatchTouchEvent()里最终调用到onTouchEvent()  
     *   
     *   
     *   
     * 重点关注:  
     * 1 子View对于ACTION_DOWN的处理十分重要!!!!!  
     *   ACTION_DOWN是一系列Touch事件的开端,如果子View对于该ACTION_DOWN事件在onTouchEvent()中返回了false即未消费.  
     *   那么ViewGroup就不会把后续的ACTION_MOVE和ACTION_UP派发给该子View.在这种情况下ViewGroup就和普通的View一样了,  
     *   调用该ViewGroup自己的dispatchTouchEvent()从而调用自己的onTouchEvent();即不会将事件分发给子View.  
     *   详细代码请参见如下代码分析.  
     *     
     * 2 为什么子view对于Touch事件处理返回true那么其上层的ViewGroup就无法处理Touch事件了?????  
     *   这个想必大家都知道了,因为该Touch事件被子View消费了其上层的ViewGroup就无法处理该Touch事件了.  
     *   那么在源码中的依据是什么呢??请看下面的源码分析  
     */    

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

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

            /**  
             * 第一步:对于ACTION_DOWN进行处理(Handle an initial down)  
             * 因为ACTION_DOWN是一系列事件的开端,当是ACTION_DOWN时进行一些初始化操作.  
             * 从源码的注释也可以看出来:清除以往的Touch状态(state)开始新的手势(gesture)  
             * cancelAndClearTouchTargets(ev)中有一个非常重要的操作:  
             * 将mFirstTouchTarget设置为null!!!!  
             * 随后在resetTouchState()中重置Touch状态标识  
             */    
            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)  
             * 在dispatchTouchEvent(MotionEventev)这段代码中  
             * 使用变量intercepted来标记ViewGroup是否拦截Touch事件的传递.  
             * 该变量在后续代码中起着很重要的作用.  
             */    
            final boolean intercepted;    
            // 事件为ACTION_DOWN或者mFirstTouchTarget不为null(即已经找到能够接收touch事件的目标组件)时if成立    
            if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {    
                //判断disallowIntercept(禁止拦截)标志位    
                //因为在其他地方可能调用了requestDisallowInterceptTouchEvent(boolean disallowIntercept)    
                //从而禁止执行是否需要拦截的判断(有点拗口~其实看requestDisallowInterceptTouchEvent()方法名就可明白)    
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;    
                //当没有禁止拦截判断时(即disallowIntercept为false)调用onInterceptTouchEvent(ev)方法    
                if (!disallowIntercept) {    
                    //既然disallowIntercept为false那么就调用onInterceptTouchEvent()方法将结果赋值给intercepted    
                    //常说事件传递中的流程是:dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent    
                    //其实在这就是一个体现,在dispatchTouchEvent()中调用了onInterceptTouchEvent()    
                    intercepted = onInterceptTouchEvent(ev);    
                    ev.setAction(action); // restore action in case it was changed    
                } else {    
                     //当禁止拦截判断时(即disallowIntercept为true)设置intercepted = false    
                    intercepted = false;    
                }    
            } else {    
                //当事件不是ACTION_DOWN并且mFirstTouchTarget为null(即没有Touch的目标组件)时    
                //设置 intercepted = true表示ViewGroup执行Touch事件拦截的操作。    
                //There are no touch targets and this action is not an initial down    
                //so this view group continues to intercept touches.    
                intercepted = true;    
            }    


            /**  
             * 第三步:检查cancel(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;    
            //不是ACTION_CANCEL并且ViewGroup的拦截标志位intercepted为false(不拦截)    
            if (!canceled && !intercepted) {    
                //处理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 (childrenCount != 0) {    
                        // 依据Touch坐标寻找子View来接收Touch事件    
                        // Find a child that can receive the event.    
                        // Scan children from front to back.    
                        final View[] children = mChildren;    
                        final float x = ev.getX(actionIndex);    
                        final float y = ev.getY(actionIndex);    

                        final boolean customOrder = isChildrenDrawingOrderEnabled();    
                        // 遍历子View判断哪个子View接受Touch事件    
                        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;    
                            }    

                            newTouchTarget = getTouchTarget(child);    
                            if (newTouchTarget != null) {    
                                // 找到接收Touch事件的子View!!!!!!!即为newTouchTarget.    
                                // 既然已经找到了,所以执行break跳出for循环    
                                // 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不满足,当然也不会执行break语句.  
                             * 于是代码会执行到这里来.  
                             *   
                             * 调用方法dispatchTransformedTouchEvent()将Touch事件传递给子View做  
                             * 递归处理(也就是遍历该子View的View树)  
                             * 该方法很重要,看一下源码中关于该方法的描述:  
                             * 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.  
                             * 将Touch事件传递给特定的子View.  
                             * 该方法十分重要!!!!在该方法中为一个递归调用,会递归调用dispatchTouchEvent()方法!!!!!!!!!!!!!!  
                             * 在dispatchTouchEvent()中:  
                             * 如果子View为ViewGroup并且Touch没有被拦截那么递归调用dispatchTouchEvent()  
                             * 如果子View为View那么就会调用其onTouchEvent(),这个就不再赘述了.  
                             *   
                             *   
                             * 该方法返回true则表示子View消费掉该事件,同时进入该if判断.  
                             * 满足if语句后重要的操作有:  
                             * 1 给newTouchTarget赋值  
                             * 2 给alreadyDispatchedToNewTouchTarget赋值为true.  
                             *   看这个比较长的英语名字也可知其含义:已经将Touch派发给新的TouchTarget  
                             * 3 执行break.  
                             *   因为该for循环遍历子View判断哪个子View接受Touch事件,既然已经找到了  
                             *   那么就跳出该for循环.  
                             * 4 注意:  
                             *   如果dispatchTransformedTouchEvent()返回false即子View  
                             *   的onTouchEvent返回false(即Touch事件未被消费)那么就不满足该if条件,也就无法执行addTouchTarget()  
                             *   从而导致mFirstTouchTarget为null.那么该子View就无法继续处理ACTION_MOVE事件  
                             *   和ACTION_UP事件!!!!!!!!!!!!!!!!!!!!!!  
                             * 5 注意:  
                             *   如果dispatchTransformedTouchEvent()返回true即子View  
                             *   的onTouchEvent返回true(即Touch事件被消费)那么就满足该if条件.  
                             *   从而mFirstTouchTarget不为null!!!!!!!!!!!!!!!!!!!  
                             * 6 小结:  
                             *   对于此处ACTION_DOWN的处理具体体现在dispatchTransformedTouchEvent()  
                             *   该方法返回boolean,如下:  
                             *   true---->事件被消费----->mFirstTouchTarget!=null  
                             *   false--->事件未被消费---->mFirstTouchTarget==null  
                             *   因为在dispatchTransformedTouchEvent()会调用递归调用dispatchTouchEvent()和onTouchEvent()  
                             *   所以dispatchTransformedTouchEvent()的返回值实际上是由onTouchEvent()决定的.  
                             *   简单地说onTouchEvent()是否消费了Touch事件(true or false)的返回值决定了dispatchTransformedTouchEvent()  
                             *   的返回值!!!!!!!!!!!!!从而决定了mFirstTouchTarget是否为null!!!!!!!!!!!!!!!!从而进一步决定了ViewGroup是否  
                             *   处理Touch事件.这一点在下面的代码中很有体现.  
                             *     
                             *   
                             */    
                            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();    
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);    
                                alreadyDispatchedToNewTouchTarget = true;    
                                break;    
                            }    
                        }    
                    }    


                    /**  
                     * 该if条件表示:  
                     * 经过前面的for循环没有找到子View接收Touch事件并且之前的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;    
                        }    
                        //newTouchTarget指向了最初的TouchTarget    
                        newTouchTarget.pointerIdBits |= idBitsToAssign;    
                    }    
                }    
            }    



            /**  
             * 分发Touch事件至target(Dispatch to touch targets)  
             *   
             * 经过上面对于ACTION_DOWN的处理后mFirstTouchTarget有两种情况:  
             * 1 mFirstTouchTarget为null  
             * 2 mFirstTouchTarget不为null  
             *   
             * 当然如果不是ACTION_DOWN就不会经过上面较繁琐的流程  
             * 而是从此处开始执行,比如ACTION_MOVE和ACTION_UP  
             */    
            if (mFirstTouchTarget == null) {    
                /**  
                 * 情况1:mFirstTouchTarget为null  
                 *   
                 * 经过上面的分析mFirstTouchTarget为null就是说Touch事件未被消费.  
                 * 即没有找到能够消费touch事件的子组件或Touch事件被拦截了,  
                 * 则调用ViewGroup的dispatchTransformedTouchEvent()方法处理Touch事件则和普通View一样.  
                 * 即子View没有消费Touch事件,那么子View的上层ViewGroup才会调用其onTouchEvent()处理Touch事件.  
                 * 在源码中的注释为:No touch targets so treat this as an ordinary view.  
                 * 也就是说此时ViewGroup像一个普通的View那样调用dispatchTouchEvent(),且在dispatchTouchEvent()  
                 * 中会去调用onTouchEvent()方法.  
                 * 具体的说就是在调用dispatchTransformedTouchEvent()时第三个参数为null.  
                 * 第三个参数View child为null会做什么样的处理呢?  
                 * 请参见下面dispatchTransformedTouchEvent()的源码分析  
                 *   
                 * 这就是为什么子view对于Touch事件处理返回true那么其上层的ViewGroup就无法处理Touch事件了!!!!!!!!!!  
                 * 这就是为什么子view对于Touch事件处理返回false那么其上层的ViewGroup才可以处理Touch事件!!!!!!!!!!  
                 *   
                 */    
                handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);    
            } else {    
                /**  
                 * 情况2:mFirstTouchTarget不为null即找到了可以消费Touch事件的子View且后续Touch事件可以传递到该子View  
                 * 在源码中的注释为:  
                 * 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;    
                        //对于非ACTION_DOWN事件继续传递给目标子组件进行处理,依然是递归调用dispatchTransformedTouchEvent()    
                        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;    
                }    
            }    

            /**  
             * 处理ACTION_UP和ACTION_CANCEL  
             * 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;    
    }    



    //=====================以上为dispatchTouchEvent()源码分析======================    



    //===============以下为dispatchTransformedTouchEvent()源码分析=================    

    /**  
     * 在dispatchTouchEvent()中调用dispatchTransformedTouchEvent()将事件分发给子View处理  
     *   
     * 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.  
     *   
     * 在此请着重注意第三个参数:View child  
     * 在dispatchTouchEvent()中多次调用了dispatchTransformedTouchEvent(),但是有时候第三个参数为null,有时又不是.  
     * 那么这个参数是否为null有什么区别呢?  
     * 在如下dispatchTransformedTouchEvent()源码中可见多次对于child是否为null的判断,并且均做出如下类似的操作:  
     * if (child == null) {  
     *       handled = super.dispatchTouchEvent(event);  
     *    } else {  
     *       handled = child.dispatchTouchEvent(event);  
     * }  
     * 这个代码是什么意思呢??  
     * 当child == null时会将Touch事件传递给该ViewGroup自身的dispatchTouchEvent()处理.  
     * 即super.dispatchTouchEvent(event)正如源码中的注释描述的一样:  
     * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.  
     * 当child != null时会调用该子view(当然该view可能是一个View也可能是一个ViewGroup)的dispatchTouchEvent(event)处理.  
     * 即child.dispatchTouchEvent(event);  
     *   
     *   
     */    
    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;    
    }     

}  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值