美文网首页
事件拦截分发原理-源码分析

事件拦截分发原理-源码分析

作者: isLJli | 来源:发表于2020-09-06 01:32 被阅读0次

事件拦截分发大致流程

事件的分发由ViewGroup承担,由View负责消耗。首先是将厚书读薄,以下两图概况了ViewGroup的事件分发,和View的事件消耗。


ViewGroup的分发流程
View的消耗流程

由以上两图可以大致知道ViewGroup的拦截先是判断 requestDisallowInterceptTouchEvent()的值,其是子view的尚方宝剑的方法,子view只要把它设置为ture,那么父View则不拦截事件。如果requestDisallowInterceptTouchEvent() == false,则调onInterceptTouchEvent()方法判断是否拦截事件,其默认方法大多数都为false,所以onInterceptTouchEvent()总是我们自己写逻辑代码来拦截。如果onInterceptTouchEvent() == false,则遍历所有子View的dispatchTouchEvent(),其方法会先判断子ViewTouchListener.onTouch()== true则拦截事件,否则将继续执行onTouchEvent()方法查看是否拦截。如果所有的子view都不拦截,判断的依据是链表是否为null,则执行ViewGroup的onTouchEvent()查看是否拦截。

  • 事件拦截的大致流程已经知道,下面则开始以手势down、move的视角来细读事件分发流程,将薄书读厚。

手势分发细读

  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.
      if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
          ev.setTargetAccessibilityFocus(false);
      }


      // handled 开始赋值为false,这个是返回是否拦截的变量
      boolean handled = false;

      // onFilterTouchEventForSecurity 翻译为 安全的筛选器触摸事件 只有这个通过才开始进行手势判断
      if (onFilterTouchEventForSecurity(ev)) {
          //手势
          final int action = ev.getAction();
          final int actionMasked = action & MotionEvent.ACTION_MASK; //多点触摸

          // Handle an initial down. 可以知道每点击一次屏幕,它们的状态就会重新初始化
          if (actionMasked == MotionEvent.ACTION_DOWN) {
              //开始新的触摸手势时,放弃所有以前的状态。
              // 由于应用程序切换,ANR或其他一些状态更改,框架可能已放弃上一个手势的up或cancel事件
              cancelAndClearTouchTargets(ev);
              resetTouchState();

//                private void resetTouchState() {
//
//                    clearTouchTargets();
//                    resetCancelNextUpFlag(this);
//                    // requestDisallowInterceptTouchEvent()值重置
//                    mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
//                    mNestedScrollAxes = SCROLL_AXIS_NONE;
//                }

//                private void clearTouchTargets() {
//                    //把mFirstTouchTarget重置为null
//                    TouchTarget target = mFirstTouchTarget;
//                    if (target != null) {
//                        do {
//                            TouchTarget next = target.next;
//                            target.recycle();
//                            target = next;
//                        } while (target != null);
//                        mFirstTouchTarget = null;
//                    }
//                }

          }

          // Check for interception.
          // 检查拦截的变量
          final boolean intercepted;
          //手势为DOWM时进入,还有在子View有拦截链表时进入,这里在每次DOWN的时候进入,只要链表!= null时MOVE的时候也可以进入
          if (actionMasked == MotionEvent.ACTION_DOWN
                  || mFirstTouchTarget != null) {
              // requestDisallowInterceptTouchEvent()的值
              final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
              if (!disallowIntercept) {
                  // requestDisallowInterceptTouchEvent() == false时,则调用 onInterceptTouchEvent(ev)是否拦截其一般也不拦截
                  // 这个也是我们可以做外部拦截的自定义方法
                  intercepted = onInterceptTouchEvent(ev);
                  ev.setAction(action); // restore action in case it was changed
              } else {
                  // requestDisallowInterceptTouchEvent() == true时,不拦截
                  intercepted = false;
              }
          }
          //这种情况对应的是:MOVE时,但是链表为空即所有的子view都不拦截事件
          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, start normal event dispatch. Also if there is already
          // a view that is handling the gesture, do normal event dispatch.
          if (intercepted || mFirstTouchTarget != null) {
              ev.setTargetAccessibilityFocus(false);
          }

          // 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;
          //新建链表,是有子View拦截的判断
          TouchTarget newTouchTarget = null;
          boolean alreadyDispatchedToNewTouchTarget = false;

          //这里要非取消和非拦截 才执行
          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.
              //如果事件以可访问性焦点为目标,则将其赋予可访问性焦点的视图,如果它不处理,则清除标志并将事件照常分派给所有子级。
              // 因为这些事件非常少见,所以我们正在查找着重于可访问性的主机,以避免保持状态。
              View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                      ? findChildWithAccessibilityFocus() : null;
              //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 (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();
                      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 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.
                          if (childWithAccessibilityFocus != null) {
                              if (childWithAccessibilityFocus != child) {
                                  continue;
                              }
                              childWithAccessibilityFocus = null;
                              i = childrenCount - 1;
                          }

                          //检查点击区域是否在View区域范围内
                          if (!child.canReceivePointerEvents()
                                  || !isTransformedTouchPointInView(x, y, child, null)) {
                              ev.setTargetAccessibilityFocus(false);
                              continue;
                          }

                          newTouchTarget = getTouchTarget(child);

                          //如果已经有了子View拦截则跳出循环
                          if (newTouchTarget != null) {
                              // 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);
                          //子VIEW拦截则跳出循环
                          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
                                  //childIndex指向预排序列表,找到原始索引
                                  for (int j = 0; j < childrenCount; j++) {
                                      if (children[childIndex] == mChildren[j]) {
                                          mLastTouchDownIndex = j;
                                          break;
                                      }
                                  }
                              } else {
                                  mLastTouchDownIndex = childIndex;
                              }
                              mLastTouchDownX = ev.getX();
                              mLastTouchDownY = ev.getY();
                              newTouchTarget = addTouchTarget(child, idBitsToAssign);
                              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.
                      newTouchTarget = mFirstTouchTarget;
                      while (newTouchTarget.next != null) {
                          newTouchTarget = newTouchTarget.next;
                      }
                      newTouchTarget.pointerIdBits |= idBitsToAssign;
                  }
              }
          }

          // Dispatch to touch targets. 派遣接触目标

          // mFirstTouchTarget == null 时,表示执行上面if()方法没有找到消耗的子view或者
          // 没有执行上面的if()方法:onInterceptTouchEvent =true 或 MOVE时,但是链表为空即所有的子view都不拦截事件
          if (mFirstTouchTarget == null) {
              // No touch targets so treat this as an ordinary view.   没有触摸目标,因此请将其视为普通视图。
              //看看自己ViewGroup是否消耗
              handled = dispatchTransformedTouchEvent(ev, canceled, null,
                      TouchTarget.ALL_POINTER_IDS);
          } else {
              //有子View拦截时,就调用子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) {
                  // target.next == null
                  final TouchTarget next = target.next;
                  if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                      handled = true;
                  } else {
                      final boolean cancelChild = resetCancelNextUpFlag(target.child)
                              || intercepted;
                      // chiled =target.child ,  调用子view的消耗
                      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;
              }
          }

          // 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;
  }

上面把ViewGroup的dispatchTouchEvent()的源码读了一遍,我们发现在执行判断是requestDisallow...()onInterceptTouchEvent()时要满足if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null),也就是说如果子view拦截了事件,在MOVE时依旧会执行ViewGrouprequestDisallow...()onInterceptTouchEvent()。所以外部拦截方则可以利用ViewGroup的onInterceptTouchEvent()来判断什么时候由父亲拦截。但这里的一个主要条件是:在Down的时候有子View拦截了事件,如果没有子View拦截,那么MOVE事件将不执行onInterceptTouchEvent()方法,而是执行ViewGroup的onTouchEvent()方法。

  • 所以外部拦截法:是利用有子View拦截事件时,MOVE手势依旧会执行onInterceptTouchEvent()方法,然后在这个方法中判断子View是否滑到头来决定是否拦截。

  • 内部拦截法:利用事件分发原则,在DOWN时设置requestDisallowInterceptTouchEvent(true)总是拦截事件,然后在MOVE时根据自己是否滑到头决定是否不拦截设置为requestDisallowInterceptTouchEvent(false)

  • NestedScrolling嵌套滑动:子View在onTouchEvent()的DOWN时调用requestDisallowInterceptTouchEvent(true)拦截事件,在MOVE时利用接口的实现传递距离、方向等信息与父View共同完成嵌套滑动

总结:基于以上代码,我们可以得出三种解决嵌套滑动方案:外部和内部拦截、NestedScrolling机制。下一篇介绍三种解决嵌套滑动方法。

相关文章

网友评论

      本文标题:事件拦截分发原理-源码分析

      本文链接:https://www.haomeiwen.com/subject/kqkbdktx.html