事件分发机制(二)--ViewGroup

130 阅读2分钟

「这是我参与11月更文挑战的第24天,活动详情查看:2021最后一次更文挑战

1. ViewGroup.dispatchTouchEvent()

  • ViewGroup每次事件分发时,都需调用 onInterceptTouchEvent() 询问是否拦截事件
  • disallowIntercept:是否禁用事件拦截的功能(默认是false),可通过调用requestDisallowInterceptTouchEvent()修改
  • 通过for循环,遍历了当前ViewGroup下的所有子View ViewGroup.png
源码分析

这里主要是判断是否有拦截,调用 onInterceptTouchEvent(ev) 来判断是否有拦截。如果VieGroup的onInterceptTouchEvent()第一次执行为true,则mFirstTouchTarget = null,则也会使得接下来不会调用onInterceptTouchEvent(),直接将拦截设置为true

  final boolean intercepted;
  if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    if (!disallowIntercept) {
      intercepted = onInterceptTouchEvent(ev);
      ev.setAction(action); 
    } else {
      intercepted = false;
    }
  } else {
    intercepted = true;
  }

从父视图开始遍历

  • (1)如果当前视图无法获取用户焦点,跳过本次循环
  • (2)如果view不可见,或者触摸的坐标点不在view的范围内,跳过本次循环
  • (3)寻找newTouchTarget,如果已经存在newTouchTarget,说明正在接收触摸事件,跳出循环
  • (4)如果触摸位置在child的区域内,把事件分发给子View或ViewGroup
  • (5)获取TouchDown的x,y坐标
  • (6)dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign) 是调用子元素的dispatchTouchEvent()方法 , 传递的child不为空,所以就会调用子元素的dispatchTouchEvent()
for (int i = childrenCount - 1; i >= 0; i--) {
  final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
  final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
  // (1)
  if (childWithAccessibilityFocus != null) {
    if (childWithAccessibilityFocus != child) {
      continue;
    }
    childWithAccessibilityFocus = null;
    i = childrenCount - 1;
  }
  // (2)
  if (!child.canReceivePointerEvents()|| !isTransformedTouchPointInView(x, y, child, null)) {
    ev.setTargetAccessibilityFocus(false);
    continue;
  }
  // (3)
  newTouchTarget = getTouchTarget(child);
  if (newTouchTarget != null) {
    newTouchTarget.pointerIdBits |= idBitsToAssign;
    break;
  }
  . . .
  // (4)
  if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
    mLastTouchDownTime = ev.getDownTime();
    if (preorderedList != null) {
      for (int j = 0; j < childrenCount; j++) {
        if (children[childIndex] == mChildren[j]) {
          mLastTouchDownIndex = j;
          break;
        }
      }
    } else {
      mLastTouchDownIndex = childIndex;
    }
    // (5)
    mLastTouchDownX = ev.getX();
    mLastTouchDownY = ev.getY();
    newTouchTarget = addTouchTarget(child, idBitsToAssign);
    alreadyDispatchedToNewTouchTarget = true;
    break;
  }
  . . .
}

onInterceptTouchEvent()

  • 返回false:不拦截(默认)
  • 返回true:拦截,即事件停止往下传递(需复写onInterceptTouchEvent()其返回true)
  • 在onInterceptTouchEvent返回TURE表示拦截时,实际调用的是super.dispatchTouchEvent方法,即View的该方法,进而由该方法调用onTouchEvent
public boolean onInterceptTouchEvent(MotionEvent ev) {
  if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
                && ev.getAction() == MotionEvent.ACTION_DOWN
                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
                && isOnScrollbarThumb(ev.getX(), ev.getY())) {
    return true;
  }
  return false;
}

2. 总结

  • 当前ViewGroup拦截了事件链中的任何一次事件,那么该事件链后续的事件都会被拦截下来,即onInterceptTouchEvent() 返回false;并且后续的事件也不会再走onInterceptTouchEvent()
  • 能接收事件的View要么是可见的,要么设置了动画
  • 整个事件分发流程的主要就是在ViewGroup,因为View是不具备分发功能