MyException - 我的异常网
当前位置:我的异常网» Android » Android事件散发详解(三)——ViewGroup的dispatchTo

Android事件散发详解(三)——ViewGroup的dispatchTouchEvent()源码学习

www.MyException.Cn  网友分享于:2014-12-30  浏览:0次
Android事件分发详解(三)——ViewGroup的dispatchTouchEvent()源码学习
package cc.aa;

import android.os.Environment;
import android.view.MotionEvent;
import android.view.View;

public class UnderstandDispatchTouchEvent {
    /**
     * 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事件了.
     *   那么在源码中的依据是什么呢??请看下面的源码分析
     *   
     * 参考资料:
     * 0 http://blog.csdn.net/sahadev_/article/details/23839039
     * 1 http://www.cnblogs.com/xiaoweiz/p/3838682.html
     * 2 http://hukai.me/android-deeper-touch-event-dispatch-process/
     * 3 http://blog.csdn.net/hdxiaoyu2/article/details/25563453
     * 4 http://www.eoeandroid.com/thread-542296-1-1.html
     * 5 http://www.eoeandroid.com/forum.php?mod=viewthread&tid=319301
     * 6 http://stackvoid.com/details-dispatch-onTouch-Event-in-Android/
     *   Thank you very much
     */
	
    @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;
    }
    
    
    
    
    
    

}

文章评论

为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
我的丈夫是个程序员
我的丈夫是个程序员
程序员必看的十大电影
程序员必看的十大电影
程序员应该关注的一些事儿
程序员应该关注的一些事儿
程序员的鄙视链
程序员的鄙视链
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
中美印日四国程序员比较
中美印日四国程序员比较
程序员和编码员之间的区别
程序员和编码员之间的区别
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
2013年中国软件开发者薪资调查报告
2013年中国软件开发者薪资调查报告
每天工作4小时的程序员
每天工作4小时的程序员
代码女神横空出世
代码女神横空出世
总结2014中国互联网十大段子
总结2014中国互联网十大段子
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
一个程序员的时间管理
一个程序员的时间管理
如何成为一名黑客
如何成为一名黑客
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
那些性感的让人尖叫的程序员
那些性感的让人尖叫的程序员
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
Java程序员必看电影
Java程序员必看电影
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
那些争议最大的编程观点
那些争议最大的编程观点
我是如何打败拖延症的
我是如何打败拖延症的
为什么程序员都是夜猫子
为什么程序员都是夜猫子
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
2013年美国开发者薪资调查报告
2013年美国开发者薪资调查报告
10个调试和排错的小建议
10个调试和排错的小建议
 程序员的样子
程序员的样子
旅行,写作,编程
旅行,写作,编程
漫画:程序员的工作
漫画:程序员的工作
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
程序员都该阅读的书
程序员都该阅读的书
老程序员的下场
老程序员的下场
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
我跳槽是因为他们的显示器更大
我跳槽是因为他们的显示器更大
鲜为人知的编程真相
鲜为人知的编程真相
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有