CoordinatorLayout和Behavior(四)

本文深入解析CoordinatorLayout中View之间的依赖关系及其回调机制。探讨如何利用XML属性和Behavior方法定义依赖,详细说明不同事件触发下的视图变化及Behavior的响应。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Behavior和View依赖

可以通过XML的layout_anchor属性和Behavior的layoutDependsOn()方法两种方式来指定CoordinatorLayout的子View间的依赖关系。

当被依赖View被删除时,Behavior的onDependentViewRemoved()方法会被回调。当被依赖View的布局发生变化时,Behavior的onDependentViewChanged()方法会被回调。

CoordinatorLayout通过onChildViewsChanged方法来实现Behavior的方法回调。该方法的参数type有3种值,EVENT_NESTED_SCROLL、EVENT_VIEW_REMOVED、EVENT_PRE_DRAW,分别对应3中不同的情形。

在Behavior的嵌套滚动方法中,会在滚动后调用onChildViewsChanged方法,type为EVENT_NESTED_SCROLL。

CoordinatorLayout在构造函数中注册了OnHierarchyChangeListener监听器。

super.setOnHierarchyChangeListener(new HierarchyChangeListener());

@Override
public void setOnHierarchyChangeListener(OnHierarchyChangeListener onHierarchyChangeListener) {
  mOnHierarchyChangeListener = onHierarchyChangeListener;
}
复制代码

HierarchyChangeListener在View被移除时,调用onChildViewsChanged方法,type为EVENT_VIEW_REMOVED。

private class HierarchyChangeListener implements OnHierarchyChangeListener {
  HierarchyChangeListener() {
  }

  @Override
  public void onChildViewAdded(View parent, View child) {
    if (mOnHierarchyChangeListener != null) {
      mOnHierarchyChangeListener.onChildViewAdded(parent, child);
    }
  }

  @Override
  public void onChildViewRemoved(View parent, View child) {
    onChildViewsChanged(EVENT_VIEW_REMOVED);

    if (mOnHierarchyChangeListener != null) {
      mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
    }
  }
}
复制代码

Coordinator在onMeasure方法中通过ensurePreDrawListener方法注册了ViewTreeObserver.OnPreDrawListener 监听器。在每次绘制View前调用调用onChildViewsChanged方法,type为EVENT_PRE_DRAW。

class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
  @Override
  public boolean onPreDraw() {
    onChildViewsChanged(EVENT_PRE_DRAW);
    return true;
  }
}
复制代码

onChildViewsChanged方法会根据锚点的位置和Insets调整View的位置,然后再回调Behavior的方法。

final void onChildViewsChanged(@DispatchChangeEvent final int type) {
  final int layoutDirection = ViewCompat.getLayoutDirection(this);
  final int childCount = mDependencySortedChildren.size();
  final Rect inset = mTempRect4;
  inset.setEmpty();
  // 遍历根据依赖关系排好序的View
  for (int i = 0; i < childCount; i++) {
    final View child = mDependencySortedChildren.get(i);
    final LayoutParams lp = (LayoutParams) child.getLayoutParams();

    // 寻找排在前面的View是否有锚点
    for (int j = 0; j < i; j++) {
      final View checkChild = mDependencySortedChildren.get(j);
      // 如果有锚点
      if (lp.mAnchorDirectChild == checkChild) {
          // offsetChildToAnchor方法会根据锚点调整子View的位置,并回调onDependentViewChanged方法。
          offsetChildToAnchor(child, layoutDirection);
      }
    }

    final Rect drawRect = mTempRect1;
    getChildRect(child, true, drawRect);

    if (lp.insetEdge != Gravity.NO_GRAVITY && !drawRect.isEmpty()) {
      final int absInsetEdge = GravityCompat.getAbsoluteGravity(
              lp.insetEdge, layoutDirection);
      switch (absInsetEdge & Gravity.VERTICAL_GRAVITY_MASK) {
        case Gravity.TOP:
          inset.top = Math.max(inset.top, drawRect.bottom);
          break;
        case Gravity.BOTTOM:
          inset.bottom = Math.max(inset.bottom, getHeight() - drawRect.top);
          break;
      }
      switch (absInsetEdge & Gravity.HORIZONTAL_GRAVITY_MASK) {
        case Gravity.LEFT:
          inset.left = Math.max(inset.left, drawRect.right);
          break;
        case Gravity.RIGHT:
          inset.right = Math.max(inset.right, getWidth() - drawRect.left);
          break;
      }
    }

    if (lp.dodgeInsetEdges != Gravity.NO_GRAVITY && child.getVisibility() == View.VISIBLE) {
      // 调整子View的Inset
      offsetChildByInset(child, inset, layoutDirection);
    }

    if (type == EVENT_PRE_DRAW) {
      final Rect lastDrawRect = mTempRect2;
      getLastChildRect(child, lastDrawRect);
      // 如果布局没有变化,则遍历下一个
      if (lastDrawRect.equals(drawRect)) {
        continue;
      }
      // 如果变化了,要记录上一次的位置。
      recordLastChildRect(child, drawRect);
    }

    // 遍历排在后面的View,看是否依赖当前的子View。
    for (int j = i + 1; j < childCount; j++) {
      final View checkChild = mDependencySortedChildren.get(j);
      final LayoutParams checkLp = (LayoutParams) checkChild.getLayoutParams();
      final Behavior b = checkLp.getBehavior();
      // 如果Behavior的layoutDependsOn返回true。
      if (b != null && b.layoutDependsOn(this, checkChild, child)) {
        if (type == EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) {
          // 如果是EVENT_PRE_DRAW但已经在EVENT_NESTED_SCROLL中处理过了,则遍历下一个。
          checkLp.resetChangedAfterNestedScroll();
          continue;
        }

        final boolean handled;
        switch (type) {
          case EVENT_VIEW_REMOVED:
            // 如果是移除事件则调用onDependentViewRemoved方法。
            b.onDependentViewRemoved(this, checkChild, child);
            handled = true;
            break;
          default:
            // 如果是EVENT_NESTED_SCROLL或者EVENT_PRE_DRAW,调用onDependentViewChanged方法。
            handled = b.onDependentViewChanged(this, checkChild, child);
            break;
        }

        if (type == EVENT_NESTED_SCROLL) {
          // 如果是EVENT_NESTED_SCROLL,则记录onDependentViewChanged的处理结果
          checkLp.setChangedAfterNestedScroll(handled);
        }
      }
    }
  }
}
复制代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值