viewGroup的 dispatchTouchEvent 方法逻辑中对于事件拦截部分的源码分析如下:
ViewGroup.java api29 public boolean dispatchTouchEvent(MotionEvent ev) { ... // 对遮盖状态进行过滤 if (onFilterTouchEventForSecurity(ev)) { ... // 判断是否需要拦截 final boolean intercepted; // down事件或者有target的非down事件则需要判断是否需要拦截 // 否则不需要进行拦截判断,因为一定是交给自己处理 if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { // 此标志为子view通过requestDisallowInterupt方法设置 // 禁止viewGroup拦截事件 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { // 调用onInterceptTouchEvent判断是否需要拦截 intercepted = onInterceptTouchEvent(ev); // 恢复事件状态 ev.setAction(action); } else { intercepted = false; } } else { // 自己消费了down事件,那么后续的事件非down事件都是自己处理 intercepted = true; } ...; } ...; } 寻找消费down事件的子控件对于每一个down事件,不管是ACTION_DOWN还是ACTION_POINTER_DOWN,viewGroup都会优先在控件树中寻找合适的子控件来消费他。因为对于每一个down事件,标志着一个触控点的一个崭新的事件序列,viewGroup会尽自己的最大能力寻找合适的子控件。如果找不到合适的子控件,才会自己处理down事件。因为,消费了down事件,意味着接下来该触控点的事件序列事件都会交给该view消费,如果viewGroup拦截了事件,那么子view就无法接收到任何事件消息。
viewGroup寻找子控件的步骤也不复杂。首先viewGroup会为他的子控件构造一个控件列表,构造的顺序是view的绘制顺序的逆序,也就是一个view的z轴系数越高,显示高度越高,在列表的顺序就会越靠前。这其实比较好理解,显示越高的控件肯定是优先接收点击的。除了默认情况,我们也可以进行自定义列表顺序,这里就不展开了。
viewGroup会按顺序遍历整个列表,判断触控点的位置是否在该view的范围内、该view是否可以点击等,寻找合适的子view。如果找到合适的子view,则会把down事件分发给他,如果该view接收事件,则会为他创建一个TouchTarget,将该触控id和view进行绑定,之后该触控点的事件就可以直接分发给他了。
而如果没有一个控件适合,那么会默认选取TouchTarget链表的最新一个节点。也就是当我们多点触控时,两次手指按下,如果没有找到合适的子view,那么就被认为是和上一个手指点击的是同个view。因此,如果viewGroup当前有正在消费事件的子控件,那么viewGroup自己是不会消费down事件的。
接下来我们看看源码分析(代码有点长,需要慢慢分析理解):
ViewGroup.java api29 public boolean dispatchTouchEvent(MotionEvent ev) { ... // 对遮盖状态进行过滤 if (onFilterTouchEventForSecurity(ev)) { // action的高9-16位表示索引值 // 低1-8位表示事件类型 // 只有down或者up事件才有索引值 final int action = ev.getAction(); // 获取到真正的事件类型 final int actionMasked = action & MotionEvent.ACTION_MASK; ... // 拦截内容的逻辑 if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { ... } ... // 三个变量: // split表示是否需要对事件进行分裂,对应多点触摸事件 // newTouchTarget 如果是down或pointer_down事件的新的绑定target // alreadyDispatchedToNewTouchTarget 表示事件是否已经分发给targetview了 final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; // 如果没有取消和拦截进入分发 if (!canceled && !intercepted) { ... // down或pointer_down事件,表示新的手指按下了,需要寻找接收事件的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(); // 这个整型变量记录了TouchTarget中view所对应的触控点id // 触控点id的范围是0-31,整型变量中哪一个二进制位为1,则对应绑定该id的触控点 // 例如 00000000 00000000 00000000 10001000 // 则表示绑定了id为3和id为7的两个触控点 // 这里根据是否需要分离,对触控点id进行记录, // 而如果不需要分离,则默认接收所有触控点的事件 final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS; // down事件表示该触控点事件序列是一个新的序列 // 清除之前绑定到到该触控id的TouchTarget removePointersFromTouchTargets(idBitsToAssign); final int childrenCount = mChildrenCount; // 如果子控件数目不为0而且还没绑定到新的id if (newTouchTarget == null && childrenCount != 0) { // 使用触控点索引获取触控点位置 final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); // 从前到后创建view列表 final ArrayList<View> preorderedList = buildTouchDispatchChildList(); // 判断是否是自定义view顺序 final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; // 遍历所有子控件 for (int i = childrenCount - 1; i >= 0; i--) { // 从子控件列表中获取到子控件 final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); ... // 检查该子view是否可以接受触摸事件和是否在点击的范围内 if (!child.canReceivePointerEvents() || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; } // 检查该子view是否在touchTarget链表中 newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { // 链表中已经存在该子view,说明这是一个多点触摸事件 // 即两次都触摸到同一个view上 // 将新的触控点id绑定到该TouchTarget上 newTouchTarget.pointerIdBits |= idBitsToAssign; break; } resetCancelNextUpFlag(child); // 找到合适的子view,把事件分发给他,看该子view是否消费了down事件 // 如果消费了,需要生成新的TouchTarget // 如果没有消费,说明子view不接受该down事件,继续循环寻找合适的子控件 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // 保存该触控事件的相关信息 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(); // 保存该view到target链表 newTouchTarget = addTouchTarget(child, idBitsToAssign); // 标记已经分发给子view,退出循环 alreadyDispatchedToNewTouchTarget = true; break; } ... }// 这里对应for (int i = childrenCount - 1; i >= 0; i--) ... }// 这里对应判断:(newTouchTarget == null && childrenCount != 0) if (newTouchTarget == null && mFirstTouchTarget != null) { // 没有子view接收down事件,直接选择链表尾的view作为target newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next != null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; } }// 这里对应if (actionMasked == MotionEvent.ACTION_DOWN...) }// 这里对应if (!canceled && !intercepted) ... }// 这里对应if (onFilterTouchEventForSecurity(ev)) ... } 派发事件