CoordinatorLayout和Behavior(五)

528 阅读3分钟
Behavior与事件处理

Behavior提供了onInterceptTouchEvent和onTouchEvent两个方法来处理触摸事件。有了这两个方法,自定义事件处理就不用去继承View重写事件处理的相关方法了。

CoordinatorLayout在onInterceptTouchEvent和onTouchEvent两个方法中把事件交给了子View的Behavior进行处理。

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
  MotionEvent cancelEvent = null;

  final int action = MotionEventCompat.getActionMasked(ev);

  // 如果action是ACTION_DOWN,则重置之前的触摸行为,以免之前有遗漏。
  if (action == MotionEvent.ACTION_DOWN) {
    resetTouchBehaviors();
  }

  // 把事件交给子View的Behavior的performIntercept方法处理。
  final boolean intercepted = performIntercept(ev, TYPE_ON_INTERCEPT);

  if (cancelEvent != null) {
    cancelEvent.recycle();
  }

  if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
    resetTouchBehaviors();
  }
  // 返回子View的Behavior的performIntercept方法处理结果。
  return intercepted;
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
  boolean handled = false;
  boolean cancelSuper = false;
  MotionEvent cancelEvent = null;

  final int action = MotionEventCompat.getActionMasked(ev);

  if (mBehaviorTouchView != null || (cancelSuper = performIntercept(ev, TYPE_ON_TOUCH))) {
    // Safe since performIntercept guarantees that
    // mBehaviorTouchView != null if it returns true
    final LayoutParams lp = (LayoutParams) mBehaviorTouchView.getLayoutParams();
    final Behavior b = lp.getBehavior();
    if (b != null) {
      // 调用Behavior的onTouchEvent方法
      handled = b.onTouchEvent(this, mBehaviorTouchView, ev);
    }
  }

  // Keep the super implementation correct
  if (mBehaviorTouchView == null) {
    handled |= super.onTouchEvent(ev);
  } else if (cancelSuper) {
    if (cancelEvent == null) {
      final long now = SystemClock.uptimeMillis();
      cancelEvent = MotionEvent.obtain(now, now,
              MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
    }
    super.onTouchEvent(cancelEvent);
  }

  if (!handled && action == MotionEvent.ACTION_DOWN) {

  }

  if (cancelEvent != null) {
    cancelEvent.recycle();
  }

  if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
    resetTouchBehaviors();
  }

  return handled;
}

// 重置当前的触摸行为,为新的事件流做好准备
private void resetTouchBehaviors() {
  // 给当前处理事件的Behavior发生CANCEL事件,结束事件流
  if (mBehaviorTouchView != null) {
    final Behavior b = ((LayoutParams) mBehaviorTouchView.getLayoutParams()).getBehavior();
    if (b != null) {
      final long now = SystemClock.uptimeMillis();
      final MotionEvent cancelEvent = MotionEvent.obtain(now, now,
              MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
      b.onTouchEvent(this, mBehaviorTouchView, cancelEvent);
      cancelEvent.recycle();
    }
    mBehaviorTouchView = null;
  }
  // 重置每个子View的mDidBlockInteraction为false。
  final int childCount = getChildCount();
  for (int i = 0; i < childCount; i++) {
    final View child = getChildAt(i);
    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    lp.resetTouchBehaviorTracking();
  }
  mDisallowInterceptReset = false;
}

private boolean performIntercept(MotionEvent ev, final int type) {
  boolean intercepted = false;
  boolean newBlock = false;

  MotionEvent cancelEvent = null;

  final int action = MotionEventCompat.getActionMasked(ev);

  final List<View> topmostChildList = mTempList1;
  // 获取排好序的子View,越顶层的View越靠前
  getTopSortedChildren(topmostChildList);

  // 从顶层View向底层View进行迭代
  final int childCount = topmostChildList.size();
  for (int i = 0; i < childCount; i++) {
    final View child = topmostChildList.get(i);
    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    final Behavior b = lp.getBehavior();
    
     // 这里看不懂
    if ((intercepted || newBlock) && action != MotionEvent.ACTION_DOWN) {
      // Cancel all behaviors beneath the one that intercepted.
      // If the event is "down" then we don't have anything to cancel yet.
      if (b != null) {
          if (cancelEvent == null) {
            final long now = SystemClock.uptimeMillis();
            cancelEvent = MotionEvent.obtain(now, now,
                    MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
          }
          switch (type) {
            case TYPE_ON_INTERCEPT:
              b.onInterceptTouchEvent(this, child, cancelEvent);
              break;
            case TYPE_ON_TOUCH:
              b.onTouchEvent(this, child, cancelEvent);
              break;
          }
      }
      continue;
    }
    // 如果还没被处理且Behavior不为空,则调用Behavior的相关方法。
    if (!intercepted && b != null) {
      switch (type) {
        case TYPE_ON_INTERCEPT:
          intercepted = b.onInterceptTouchEvent(this, child, ev);
          break;
        case TYPE_ON_TOUCH:
          intercepted = b.onTouchEvent(this, child, ev);
          break;
      }
      // 如果Behavior处理了,则mBehaviorTouchView则为Behavior的对应View。
      if (intercepted) {
        mBehaviorTouchView = child;
      }
    }
    
    // 这里看不懂
    // Don't keep going if we're not allowing interaction below this.
    // Setting newBlock will make sure we cancel the rest of the behaviors.
    final boolean wasBlocking = lp.didBlockInteraction();
    final boolean isBlocking = lp.isBlockingInteractionBelow(this, child);
    newBlock = isBlocking && !wasBlocking;
    if (isBlocking && !newBlock) {
      // Stop here since we don't have anything more to cancel - we already did
      // when the behavior first started blocking things below this point.
      break;
    }
  }

  topmostChildList.clear();

  return intercepted;
}

private void getTopSortedChildren(List<View> out) {
  out.clear();
  // isChildrenDrawingOrderEnabled的返回值由setChildrenDrawingOrderEnabled设置。
  final boolean useCustomOrder = isChildrenDrawingOrderEnabled();
  final int childCount = getChildCount();
  for (int i = childCount - 1; i >= 0; i--) {
    // getChildDrawingOrder是获取View的绘制索引,默认是返回所给参数。
    // 只有setChildrenDrawingOrderEnabled的参数为true,getChildDrawingOrder才会被调用。
    final int childIndex = useCustomOrder ? getChildDrawingOrder(childCount, i) : i;
    final View child = getChildAt(childIndex);
    out.add(child);
  }
  // 当API >= 21时,TOP_SORTED_CHILDREN_COMPARATOR为ViewElevationComparator。
  // ViewElevationComparator比较的是View的z坐标。
  if (TOP_SORTED_CHILDREN_COMPARATOR != null) {
    Collections.sort(out, TOP_SORTED_CHILDREN_COMPARATOR);
  }
}

如果Behavior的blocksInteractionBelow方法返回true,则它下面的View将无法进行事件交互。为了显示无法进行交互的区域,会在Behavior对应的View下面绘制一层由getScrimColor和getScrimOpacity决定的颜色。

Behavior与WindowInsets

当CoordinatorLayout设置fitsSystemWindows为true时,当监听器OnApplyWindowInsetsListener监听到WindowInsets发生变化时,CoordinatorLayout会通过dispatchApplyWindowInsetsToBehaviors方法来回调Behavior的onApplyWindowInsets方法。