ViewGroup的dispatchTouchEvent

@Override
   public boolean dispatchTouchEvent(MotionEvent ev) {
   	// 一致性检验器 用于调试用途
       if (mInputEventConsistencyVerifier != null) {
           mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
       }

       // If the event targets the accessibility focused view and this is it, start
       // normal event dispatch. Maybe a descendant is what will handle the click.
       // 辅助功能,用于辅助有障碍人群使用;
   // 如果这个事件是辅助功能事件,那么他会带有一个target view,要求事件必须分发给该view
   // 如果setTargetAccessibilityFocus(false),表示取消辅助功能事件,按照常规的事件分发进行
   // 这里表示如果当前是目标target view,则取消标志,直接按照普通分发即可
   // 后面还有很多类似的代码,都是同样的道理
       if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
           ev.setTargetAccessibilityFocus(false);
       }

       boolean handled = false;
       // 对遮盖状态进行过滤
       if (onFilterTouchEventForSecurity(ev)) {
       	// action的高9=16位表示索引值
       	// 低1-8为表示事件类型
       	// 只有down或者up事件才有索引值
           final int action = ev.getAction();
           // 获取真正的事件类型
           final int actionMasked = action & MotionEvent.ACTION_MASK;

           // 判断事件是否为ACTION_DOWN 事件
           // 如果是,则初始化,把mFirstTouchTarget置为null。由于一个完整的事件序列是以DOWN开始,以UP结束,所以如果是DOWN事件,
           // 那么说明是一个新的事件序列,所以需要初始化之前的状态。这里的mFirstTouchTarget非常重要,
           // 后面会说到当ViewGroup的子元素成功处理事件的时候,mFirstTouchTarget会指向子元素,
           // Handle an initial down.
           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.
               // 这里吧mFirstTouchTarget 设置为 null
               cancelAndClearTouchTargets(ev);
               resetTouchState();
           }


           // 定义一个布尔值 判断是否拦截
           // Check for interception.
           final boolean intercepted;
           // 如果是ACTION——DOWN  或者 mFirstTouchTarget 不为空
           // 为什么要判断 比如说,如果子view消耗了ACTION_DOWN 事件,然后这里可以由View Group继续判断是否要拦截接下来的ACTION_MOVE事件之类
           // 又比如 ,如果第一次DOWN 事件最终不是由子view消耗掉的,那么显然mFirstTouch 将为null,所以也就不用判断了,直接把intercepted设置为true,此后的事件都是由这个ViewGroup处理
           // down事件 或者 有target的非down事件 则需要判断是否需要拦截
           // 否则不需要进行拦截,因为一定是交给自己处理
           if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {
           	//  此标志为子view通过request DisallowInterupr方法设置
           	// 禁止viewgroup拦截事件
               final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
               // disallowIntercept = 是否禁用事件拦截的功能(默认为false),可通过调用requestDisallowInterceptTouchEvent() 修改
               if (!disallowIntercept) {
               	// ViewGroup # onInterceptTouchEvent();
               	// 默认返回 false ,即View Group默认不拦截任何事件,如果想要让ViewGroup拦截事件,那么应该在自定义View Group中重写这个方法
               	// 
                   intercepted = onInterceptTouchEvent(ev);
                   // 恢复事件状态
                   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.
               // 自己消费了down事件,那么后续的事件非down事件搜是自己处理
               intercepted = true;
           }

           // If intercepted, start normal event dispatch. Also if there is already
           // a view that is handling the gesture, do normal event dispatch.
           // 如果已经被拦截,或者已经有了目标的view,取消辅助功能的target标志
           if (intercepted || mFirstTouchTarget != null) {
               ev.setTargetAccessibilityFocus(false);
           }

           // Check for cancelation.
           // 判断是否需要取消
       // 这里有很多种情况需要发送取消事件
       // 最常见的是viewGroup拦截了子view的ACTION_MOVE事件,导致事件序列中断
       // 那么需要发送cancel事件告知该view,让该view做一些状态恢复工作
           final boolean canceled = resetCancelNextUpFlag(this)
                   || actionMasked == MotionEvent.ACTION_CANCEL;

           // split 表示是否需要对事件进行分裂,对应多点触摸事件        
           // Update list of touch targets for pointer down, if needed.
           final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
           // newTouchTarget 如果是down 或者 pointer——down事件的 新的绑定target
           // 进行判断之前,会将newTouchTarget 和 alreadyDispatchedToNewTouchTarget 置为空
           TouchTarget newTouchTarget = null;
           // alreadyDispatchedToNewTouchTarget 表示事件是否已经分发给targetView了
           boolean alreadyDispatchedToNewTouchTarget = false;

           // 判断条件: 如果不是取消事件 以及 ViewGroup不进行拦截 
           if (!canceled && !intercepted) {

               // If the event is targeting accessibility focus we give it to the
               // view that has accessibility focus and if it does not handle it
               // we clear the flag and dispatch the event to all children as usual.
               // We are looking up the accessibility focused host to avoid keeping
               // state since these events are very rare.
               // 如果是辅助功能事件,我们会寻找他的targetview来接收这个事件
               View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                       ? findChildWithAccessibilityFocus() : null;
               // 判断条件: 事件是否是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(); // always 0 for down

               	// 这个整型变量记录了TouchTarget中view所对应的触控点id
               	// 触控点id的范围是0-31,整型变量中一个二进制为1,则对应绑定该id的触控点
          			// 这里根据是否需要分离,对触控点id进行记录
               	// 而如果不需要分离,则默认接受所有触控点的事件
                   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.
                   // down事件表示该触控点事件序列是一个新的序列
                   // 清除之前绑定到该触控点id的TouchTarget
                   removePointersFromTouchTargets(idBitsToAssign);


                   // 获取childrenCount 的值,表示该viewgroup 内部有多个子view
                   final int childrenCount = mChildrenCount;
                   // 如果子空间数目不为0,而且还没绑定到新的id
                   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.
                       // 从前到后创建view列表
                       final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                       // 判断是否是自定义vie顺序
                       final boolean customOrder = preorderedList == null
                               && isChildrenDrawingOrderEnabled();
                       final View[] children = mChildren;
                       // for循环 对所有子view 进行循环遍历
                       // 由于 以上判断了 view group 不对事件进行拦截,那么在这里就要对viewgroup内部的子view进行遍历,一个个的找能够接受事件的子view
                       // 这里注意 ,它是倒序遍历的,即从最上层的子view 开始往内层遍历,这也符合我们的习惯,因为一般来说我们对屏幕触摸,肯定是希望最上层的view来形影,而不是被覆盖在底层的view来响应
                       for (int i = childrenCount - 1; i >= 0; i--) {
                       	// 行子控件列表中获取到子控件
                           final int childIndex = getAndVerifyPreorderedIndex(
                                   childrenCount, i, customOrder);
                           final View child = getAndVerifyPreorderedView(
                                   preorderedList, children, childIndex);

                           // If there is a view that has accessibility focus we want it
                           // to get the event first and if not handled we will perform a
                           // normal dispatch. We may do a double iteration but this is
                           // safer given the timeframe.
                           // 如果是辅助功能事件则优先给对应的target先处理
                           // 如果该view不处理,再交给其他view处理
                           if (childWithAccessibilityFocus != null) {
                               if (childWithAccessibilityFocus != child) {
                                   continue;
                               }
                               childWithAccessibilityFocus = null;
                               i = childrenCount - 1;
                           }

                           // 判断 触摸点的位置 是否在子view的范围内或者子view是否在在播放动画,如果不符合 则continue,表示这个view不符合条件,开始遍历下一个子view
                           if (!canViewReceivePointerEvents(child)
                                   || !isTransformedTouchPointInView(x, y, child, null)) {
                               ev.setTargetAccessibilityFocus(false);
                               continue;
                           }

                           // 检查该子view是否在touchTarget链表中
                           newTouchTarget = getTouchTarget(child);
                           if (newTouchTarget != null) {
                               // Child is already receiving touch within its bounds.
                               // Give it the new pointer in addition to the ones it is handling.
                               // 链表中已经存在该子view,说明只认识一个多点触摸事件
                               // 即两次都触摸到同一个view上
                               // 将新的触控点id绑定到给TouchTarget上
                               newTouchTarget.pointerIdBits |= idBitsToAssign;
                               break;
                           }
                           //设置取消标志
                           // 下次再次调用这个方法就会返回true
                           resetCancelNextUpFlag(child);
                           // dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)
                           // 当传递进来的child 不为null时,就会调用子view 的dispatchTouchEvent(event)方法
                           // 子view符合所有条件的时候,事件就会在这里传递给了子view来处理,完成了 viewgroup 到 子view的事件传递,
                           // 当事件处理完毕,就会返回一个布尔值handled,该值表示子view死否消耗了事件
                           // 如果说子view的onTouchEvent()返回true,那就是消耗了事件。需要生成新的TouchTarget
                           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();
                               // addTouchTarget(child, idBitsToAssign)
                               // 把 mFirstTouchTarget 指向了child,同时把newTouchTarget也指向了child
                               // 也就是说,如果子view消耗掉了事件,那么mFirstTouchTarget就会执行子view,
                               // 执行玩之后直接break 跳出循环,无需再遍历了
                               // 保存该view到target链表
                               newTouchTarget = addTouchTarget(child, idBitsToAssign);
                               // 标记已经分发给子view,退出循环
                               alreadyDispatchedToNewTouchTarget = true;
                               break;
                           }

                           // The accessibility focus didn't handle the event, so clear
                           // the flag and do a normal dispatch to all children.
                           ev.setTargetAccessibilityFocus(false);
                       }
                       if (preorderedList != null) preorderedList.clear();
                   }

                   if (newTouchTarget == null && mFirstTouchTarget != null) {
                       // Did not find a child to receive the event.
                       // Assign the pointer to the least recently added target.
                       // 没有子view接收down事件,直接选择链表尾的view作为target
                       newTouchTarget = mFirstTouchTarget;
                       while (newTouchTarget.next != null) {
                           newTouchTarget = newTouchTarget.next;
                       }
                       newTouchTarget.pointerIdBits |= idBitsToAssign;
                   }
               }
           }


           // 如果不是ACTION_DOWN 的事件 将从以下开始处理
           // Dispatch to touch targets.
           // 首先判断 mFirstTouchTarget 是不是为null
           if (mFirstTouchTarget == null) {
               // No touch targets so treat this as an ordinary view.
               // 如果找不到子view来处理事件,那么最后会交由view group来处理事件
               // 如果 上面已经找到了一个子view来消耗事件了,那么这里的mFirstTouchTarget 不为空,就会接着往下执行
               // 经过了前面的处理,到这里touchTarget依旧为null,说明没有找到处理down事件的子控件
           	// 或者down事件被viewGroup本身消费了,所以该事件由viewGroup自己处理
           	// 这里调用了dispatchTransformedTouchEvent方法来分发事件
               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.
               // 已经有子view 消费了down事件
               TouchTarget predecessor = null;
               TouchTarget target = mFirstTouchTarget;
               // 遍历所有的TouchTarget 并把事件分发下去
               while (target != null) {
                   final TouchTarget next = target.next;
                   // 如果 子view消耗了 ACTION_DOWN 事件和别的事件,那么 alreadyDispatchedToNewTouchTarget和newTouchTarget已经有值了
                   // 所以就直接置handled 为true 返回,
                   // 如果 alreadyDispatchedToNewTouchTarget和newTouchTarget 的值为null,那么就不是ACTION_DOWN事件,即是ACTION_MOVE,
                   // ACTION_UP等别的事件的话 就会调用 ***2 出代码,把事件分发给子view

                   if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                   	// 表示事件已经在前面处理好了,无需重复处理

                       handled = true;
                   } else {
                   	// 正常分发事件或者分发取消事件
                       final boolean cancelChild = resetCancelNextUpFlag(target.child)
                               || intercepted;
                               // 调用了dispatchTransformedTouchEvent方法来分发事件
                       if (dispatchTransformedTouchEvent(ev, cancelChild,
                               target.child, target.pointerIdBits)) {
                       	// ***2
                       	// 表示事件在前面已经处理了,不需要重复处理
                           handled = true;
                       }
                       // 如果发送了取消事件,则移除该target
                       if (cancelChild) {
                           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.
           // 如果接收到取消获取up事件,说明事件序列结束
           // 直接删除所有TouchTarget
           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);
               // 如果仅仅只是一个POINTER_UP
               // 清除对应触控点的触摸信息
               removePointersFromTouchTargets(idBitsToRemove);
           }
       }

       if (!handled && mInputEventConsistencyVerifier != null) {
           mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
       }
       return handled;
   }







 /**
    * 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.
    */
 	// ViewGroup中真正执行事件派发的关键方法是dispatchTransformedTouchEvent,该方法会完成关键的事件分发逻辑
 	// 该方法接收原MotionEvent事件,是否进行取消,目标子view,以及目标子view感兴趣的触控id
 	// 如果不是取消事件这个方法会把元MotionEvent 中的触控点信息拆分出目标view感兴趣的触控点信息
 	// 如果是取消事件则不需要拆分直接发送取消事件即可
   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.
       // oldPointerIdBits 表示现在所有触控id
       // desiredPointerIdBits来自于该view所在的touchTarget,表示该view感兴趣的触控点id
       // 因为desiredPointerIdBits有可能全是1,所以需要和oldPointerIdBits进行位与
       // 得到真正可接受的触控点信息
       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.
       // 控件处于不一致的状态。正在接受事件序列却没有一个触控点id符合
       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.
       // 来自原始MotionEvent 的新的MotionEvent,只包含目标感兴趣的触控点
       // 最终派发的是这个MotionEvent
       final MotionEvent transformedEvent;
       // 两者相等,表死该view接受所有的触控点的事件
       // 这个时候transfromedEvent相当于原始MotionEvent的复制
       if (newPointerIdBits == oldPointerIdBits) {
       	// 当目标控件不存在通过setScaleX()等方法进行的变换时
       	// 为了效率会将原市事件简单地进行控件位置与棍定量变换之后
       	// 发送给目标的dispatchTouchEvent()方法返回
           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;
           }

           // 复制原始MotionEvent
           transformedEvent = MotionEvent.obtain(event);
       } else {

       	// 如果两者不等,说明需要对试驾进行拆分
       	// 只生成目标感兴趣的触控点的信息
       	// 这里返利事件包括了许该事件的类型,触控点索引等。
           transformedEvent = event.split(newPointerIdBits);
       }

       // Perform any necessary transformations and dispatch.
       // 对Motion Event的坐标系,转换为目标控件的坐标系并进行分发
       if (child == null) {
           handled = super.dispatchTouchEvent(transformedEvent);
       } else {
       	// 计算滚动量偏移
           final float offsetX = mScrollX - child.mLeft;
           final float offsetY = mScrollY - child.mTop;
           transformedEvent.offsetLocation(offsetX, offsetY);
           // 寻在scale等变换,需要进行矩阵变换
           if (! child.hasIdentityMatrix()) {
               transformedEvent.transform(child.getInverseMatrix());
           }

           // 调用子view的方法进行分发
           handled = child.dispatchTouchEvent(transformedEvent);
       }

       // Done.
       // 分发完毕,回收MotionEvent
       transformedEvent.recycle();
       return handled;
   }
posted @ 2021-03-08 14:48  passerbyzh  阅读(273)  评论(0)    收藏  举报