返回介绍

1.3 Behavior

发布于 2024-12-23 22:19:58 字数 5291 浏览 0 评论 0 收藏 0

CoordinatorLayout 中定义了 Behavior 类,它是用来辅助 layout 的工具。如果一个 CoordinatorLayout 的直接子 View 设置了 Behavior (或者通过类注解 @DefaultBehavior 指定 Behavior ),则该 Behavior 会储存在该 View 的 LayoutParam 中。

注意:不是 CoordinatorLayout 的直接子 View,设置 Behavior 是无效的。你可以看到任何一处对于 Behavior 的处理都是直接 getChildCount() 遍历。

在 Behavior 中有几类功能,我们一一进行介绍:

1.3.1 触摸响应类

Behavior 中有两个函数: onInterceptTouchEventonTouchEvent 。在 CoordinatorLayout 每次触发对应事件的时候会选择一个最适合的子 View 的 Behavior 执行对应函数。我们来看一下 CoordinatorLayout 是怎么分发和处理 Touch 事件的:

intercept

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

   final int action = MotionEventCompat.getActionMasked(ev);

   // 重置响应的 Behavoir
   if (action == MotionEvent.ACTION_DOWN) {
     resetTouchBehaviors();
   }

	// 在这里选择一个最佳 Behavior 进行处理
   final boolean intercepted = performIntercept(ev, TYPE_ON_INTERCEPT);

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

	// 重置响应的 Behavior
   if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
     resetTouchBehaviors();
   }

   return intercepted;
}

performIntercept 去选择一个最适合的 Behavior 来进行处理,这个方法不仅用于 onInterceptTouchEvent ,并且也用于 onTouchEvent ,根据传入 type 不同来识别对应方法。我们来看看它的逻辑:

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;

   // API>=21 时,使用 elevation 由低到高排列 View;API<21 时,按 View 添加顺序排列
   getTopSortedChildren(topmostChildList);

   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();

     // ...(省略代码) 如果此次判定 intercept,则对上次的 Behavior 发送 CANCEL 事件。

		// 根据传入 type 不同调用不同的方法
     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;
       }
       if (intercepted) {
         mBehaviorTouchView = child;
       }
     }

		//...(省略代码) 如果 Behavior.blocksInteractionBelow() 返回 true,则不处理后续的事件。
   }

   topmostChildList.clear();

   return intercepted;
}

1.3.2 依赖关系类

这部分比较简单,就俩函数: layoutDependsOn :返回 true 则表示对另一个 View 有依赖关系; onDependentViewChanged & onDependentViewRemoved :如果被依赖的 View 在正常 layout 之后仍有 size/position 上的变化,或者被 remove 掉,都会触发对应方法。

那么问题来了,CoordinatorLayout 是怎么监听这个被依赖的 View 改变的事件的呢?

原来它里面有一个 ViewTreeObserver.OnPreDrawListener ,它在 onMeasure 的时候被添加到了 ViewTreeObserver 中,这样每一帧被绘制出来之前都会调用这个回调。

class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
  @Override
  public boolean onPreDraw() {
    dispatchOnDependentViewChanged(false);
    return true;
  }
}

这个 dispatchOnDependentViewChanged 里面代码比较多,就不放上来了,总结下来就是这样:

根据依赖关系遍历子 View,对每一个 View 做如下操作

  • 判断一下新的布局边界与 lastChildRect 是否相同,是则记录新的布局边界为 lastChildRect,并继续后续流程,否则跳过;
  • 对于之后每一个 View,如果它依赖于本 View,则调用它的 Behavior.onDependentViewChanged (如果有 Behavior 的话)。

至于 onDependentViewRemoved ,是在初始化的时候就会调用 ViewGroup.setOnHierarchyChangeListener() 方法设置一个 OnHierarchyChangeListener ,这样每次 add 和 remove 子 View 的时候就会接收到回调,同时对相应依赖关系的 View 进行处理。

1.3.3 布局类

onMeasureChild & onLayoutChild :如果重写了该方法并返回 true ,则 CoordinatorLayout 会使用 Behavior 对这个子 View 进行 measure/layout。具体的可以见下面的 Measure&Layout

1.3.4 嵌套滑动类

CoordinatorLayout 实现了 NestedScrollingParent ,当 CoordinatorLayout 内有一个支持 NestedScroll 的子 View 时,它的嵌套滑动事件通过 NestedScrollingParent 的回调分发到各直接子 View 的 Behavior 处理。虽然 Behavior 类没有实现 NestedScrollingParent ,但是实际上它的方法都有。有兴趣的同学可以去看看这个类,我们这里重点讲 CoordinatorLayout 的分发过程。

各个事件的分发过程类似,此处就举一个例子:

public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
   boolean handled = false;

   final int childCount = getChildCount();
   for (int i = 0; i < childCount; i) {
     final View view = getChildAt(i);
     final LayoutParams lp = (LayoutParams) view.getLayoutParams();
     final Behavior viewBehavior = lp.getBehavior();
     if (viewBehavior != null) {
       final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child, target,
           nestedScrollAxes);
       handled |= accepted;

       lp.acceptNestedScroll(accepted);
     } else {
       lp.acceptNestedScroll(false);
     }
   }
   return handled;
}

非常简单吧,就遍历一下直接子 View,每个都调一下对应的回调方法,只要有任何一个子 View 的 behavior 消耗了这个事件,就算消耗了这个事件。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文