Android源码分析之 ViewGroup#dispatchTouchEvent

554 阅读3分钟

事件分发机制三大方法 dispatchTouchEvent onInterceptTouchEvent onTouchEvent

ViewGroup # dispatchTouchEvent 源码详解

先提出几个问题再阅读源码

  • 1. ViewGroup的某个孩子没有消费ACTION_DOWN事件;那么,后续的 ACTION_MOVE和ACTION_UP 会响应吗 ?
  • 2. ACTION_CANCEL 事件什么时候会收到?
  • 3. requestDisallowInterceptTouchEvent 有什么作用 ?
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

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

            // ACTION_DOWN 时先初始化触摸事件 , 把之前保存的状态全部清空
            // mFirstTouchTarget = null 
            // (mGroupFlags & FLAG_DISALLOW_INTERCEPT) disallowIntercept置为false 
            if (actionMasked == MotionEvent.ACTION_DOWN) {    
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            
            //intercepted = true 条件: 
            //1. down 有子view 消费了事件 && disallowIntercept =false && 自身 onInterceptTouchEvent = true
            //2. 没有子view 消费事件
            final boolean intercepted;
         
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
            // 如果是为ACTION_DOWN 事件
            //或者 mFirstTouchTarget != null  , 表示有子view消费了此事件
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    //disallowIntercept = false 
                    //调动自身 onInterceptTouchEvent 
                    //如果ACTION_DOWN 时候返回 true ,  子view 怎么都无法响应触摸事件
                    intercepted = onInterceptTouchEvent(ev);
                    //防止onInterceptTouchEvent 中对action 修改
                    //所以重新设置一遍
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }

            // 取消辅助功能事件 , 一般不用管 , 按照正常的事件分发流程
            if (intercepted || mFirstTouchTarget != null) {
                ev.setTargetAccessibilityFocus(false);
            }

            // PFLAG_CANCEL_NEXT_UP_EVENT 标记的作用是记录View是否被临时移出Window 或 viewGroup。 
            
            //canceled = true 条件 : 
            //1. 此view 被viewgroup 或 window 移除 
            //2. actionMasked == MotionEvent.ACTION_CANCEL 
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

  
            //split 作用是判断可以把事件分发到多个子View , 多点触摸开关
            //这个同样在ViewGroup中提供了public的方法: setMotionEventSplittingEnabled ( boolean  split)来设置.
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
           

            
            ///////////////////////////子view处理触摸事件的关键代码///////////////////////////
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
            
            if (!canceled && !intercepted) {
                //没有取消 , 没有拦截  ,即 子view可以处理触摸事件
                
                // 如果是辅助功能事件,会寻找他的targetview来接收这个事件
                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                        ? findChildWithAccessibilityFocus() : null;
                

                //ACTION_POINTER_DOWN :代表用户又使用一个手指触摸到屏幕上,也就是说,在已经有一个触摸点的情况下,有新出现了一个触摸点。
                //ACTION_HOVER_MOVE : 鼠标事件 , 指针在窗口或者View区域移动,但没有按下。
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                  
                    // 多点触控会有不同的索引,获取索引号
                	// 该索引位于MotionEvent中的一个数组没索引值就是数组的下标值
                	// 只有up或者down事件才会携带索引值
                    final int actionIndex = ev.getActionIndex(); // always 0 for down
                    
                    // 这个整型变量记录了TouchTarget中view所对应的触控点id
                	// 触控点id的范围是0-31,整型变量中一个二进制为1,则对应绑定该id的触控点
           			// 这里根据是否需要分离,对触控点id进行记录
                	// 而如果不需要分离,则默认接受所有触控点的事件
                   // ALL_POINTER_IDS = -1; (-1 二进制 = 11111111111111111111111111111111)
                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;

                    //清空这个手指idBitsToAssign对应的TouchTarget链表。
                    removePointersFromTouchTargets(idBitsToAssign);

                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        
                        //倒序遍历子view
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);
                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }

                         // 满足 下面条件 子view才 可以接受触摸事件 :
                         // 条件1 : 触摸坐标(x,y)在child的可视范围之内 
                         // 条件2 : child可接受触摸事件:是指child的是可见的(VISIBLE);或者虽然不可见,但是位于动画状态。
                         // 则继续往下执行。否则,调用continue , 跳过这个子view。
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }
                            
                            // 从缓存中获取newTouchTarget 
                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                //newTouchTarget != null 表示之前处理过 
                                //重新设置手指id 跳出循环
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);
                            
                            // 调用dispatchTransformedTouchEvent()将触摸事件分发给child。
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                                    // childIndex points into presorted list, find original index
                                    for (int j = 0; j < childrenCount; j++) {
                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                //如果 child.dispatchTouchEvent(event) = true;
                                // 子view包装成TouchTarge , 头插法插入到mFirstTouchTarget 的单链表中
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }

                        }
                        if (preorderedList != null) preorderedList.clear();
                    }

                    if (newTouchTarget == null && mFirstTouchTarget != null) {
                        //这种情况 , mFirstTouchTarget!=null  ,newTouchTarget == null
                        //表示之前能消费事件 , 但是现在不行了(手指不在之前能消费那个view范围内)
                        //但是newTouchTarget = mFirstTouchTarget , 事件还是交给之前能消费那个view处理 
                        newTouchTarget = mFirstTouchTarget;
                        while (newTouchTarget.next != null) {
                            newTouchTarget = newTouchTarget.next;
                        }
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }
            /////////////////////////////////////////////////////////////////////

          
           
            if (mFirstTouchTarget == null) {
                // 没有能消费的子view . 自己super.dispatchTouchEvent处理
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;        
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                         //如果子view 消费了事件 ,  handled = true;
                        handled = true;
                    } else {
                       
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        //如果cancelChild =true , 给子view发送ACTION_CANCEL事件
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        //如果cancelChild =true , 将子view从mFirstTouchTarget 链表移除
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                        
                    }
                    //遍历mFirstTouchTarget链表
                    predecessor = target;
                    target = next;
                }
            }

            // Update list of touch targets for pointer up or cancel, if needed.
            if (canceled
                    || actionMasked == MotionEvent.ACTION_UP
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                //重置触摸状态
                resetTouchState();
            } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
                final int actionIndex = ev.getActionIndex();
                final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
                //移除idBitsToRemove 对应的TouchTarget
                removePointersFromTouchTargets(idBitsToRemove);
            }
        }
        return handled;
    }

总结 :


ACTION_DOWN 事件先去找到能处理此次事件的view , 如果子view 和自己都不能处理就返回 false , 后续的move , up事件都不会再响应 ,如果找到能处理的view , 后续的事件都交给它处理

如果子view之前能处理事件后面被父view拦截了或者从父view/window移除 , 会收到CANCEL 事件 , 不会再处理任何后续事件

子view之前能消费事件 , 但是现在不行了(手指触摸点不在之前能消费那个子view范围内) , 事件还是交给子view处理

解答问题 :


问题1 : ViewGroup的某个孩子没有消费ACTION_DOWN事件;那么,后续的 ACTION_MOVE和ACTION_UP 会响应吗 ?

答 : 如果孩子没有消费ACTION_DOWN事件 , 则mFirstTouchTarget = null , intercepted =true , 子view无法响应后续触摸事件


问题2 : 子viewACTION_CANCEL 事件什么时候会收到 ?

答 : 前提子view消费了 ACTION_DOWN 事件 , 然后 1. 父view intercepted =true 或 2. 子view 从window 或父view 移除 , 子view 才会收到一个ACTION_CANCEL 事件 , 并且后续的事件都不会处理


问题3: requestDisallowInterceptTouchEvent 有什么作用 ?

如果子view.requestDisallowInterceptTouchEvent (true) , 则intercepted =false , 父view不会拦截触摸事件


责任链设计模式思想

定义

客户端发出一个请求,链上的对象都有机会来处理这一请求,而客户端不需要知道谁是具体的处理对象。

多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。

将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。其过程实际上是一个递归调用

在事件分发机制中也有责任链设计模式的思想 :

ACTION_DOWN , 会按照层级从Acvitiy -> ViewGroup -> View 这条链路找到能处理它的view , 找到了后续的ACTION_MOVE /ACTION_ UP等事件都交给它处理

参考链接 :

Android 触摸事件机制(四) ViewGroup中触摸事件详解

Android MotionEvent之ACTION_CANCEL

Android触摸滑动全解(四)——MotionEvent详解

ViewGroup的dispatchTouchEvent