CoordinatorLayout的使用(三)——CoordinatorLayout源码分析

前两篇文章介绍了CoordinatorLayout一些基本使用方式和简单自定义的Behavior。为什么CoordinatorLayout能达到这个效果呢。这就不得不对其源码进行分析了,本篇文章就以Behavior中的常用方法为重点,然后通过分析CoordinatorLayout的源码,梳理一下Behavior的调用逻辑和流程。

一、简介

老规矩,先看下Google对其的定义。

CoordinatorLayout is a super-powered FrameLayout.

CoordinatorLayout is intended for two primary use cases:

  1. As a top-level application decor or chrome layout

  2. As a container for a specific interaction with one or more child views

By specifying Behaviors for child views of a CoordinatorLayout you can provide many different interactions within a single parent and those views can also interact with one another. View classes can specify a default behavior when used as a child of a CoordinatorLayout using the CoordinatorLayout.DefaultBehavior annotation.

Behaviors may be used to implement a variety of interactions and additional layout modifications ranging from sliding drawers and panels to swipe-dismissable elements and buttons that stick to other elements as they move and animate.

Children of a CoordinatorLayout may have an anchor. This view id must correspond to an arbitrary descendant of the CoordinatorLayout, but it may not be the anchored child itself or a descendant of the anchored child. This can be used to place floating views relative to other arbitrary content panes.

Children can specify CoordinatorLayout.LayoutParams.insetEdge to describe how the view insets the CoordinatorLayout. Any child views which are set to dodge the same inset edges byCoordinatorLayout.LayoutParams.dodgeInsetEdges will be moved appropriately so that the views do not overlap.

这巴拉巴拉说了不少东西,有兴趣的可以自己看看。就不逐句翻译了。总的来说CoordinatorLayout,是一个supper-FrameLayout,主要提供两个作用:

1、作为应用的顶层布局;

2、作为一个管理容器,管理与子View或者子View之间的交互。

我们平时使用的时候,主要是就在于第2点上,作为一个ViewGroup要想能够管理交互。那肯定就得拓展东西咯,所以我们先看下该类的一些相关的特殊的属性。

属性对应xml属性用途
AndchorIdlayout_anchor&layout_anchorGravity布局时根据自身gravitylayout_anchorGravity放置在被anchor的View中
Behaviorlayout_behavior辅助Coordinator对View进行layout、nestedScroll的处理
KeyLinelayout_keyline &keylines给Coordinator设置了keylines(整数数组)后,可以为子View设置layout_keyline="i"使其的水平位置根据对应keylines[i]进行layout。
LastChildRect记录每一次Layout的位置,从而判断是否新的一帧改变了位置

Behavior大家都听的比较多了,也是我们本篇文章的重点关注对象。这里先对其他三项进行一个说明:

AndchorId:就是布局的时候参考的View的Id。

KeyLine:也是布局的时长作为水平对齐的参考。

这两个的作用也是起到管理子View的作用。作用有点类似于RelativeLayout中子View相互参考进行布局的。只是这两个实际开发中用的很少,所以在分析的时候,可能相关信息就略过了,感兴趣的可以自己研究研究。

现在回归到我们的重点Behavior,前面也介绍了一些简单用法。这里就简单回顾下里面的方法即可,关于方法说明请参考这篇文章

onInterceptTouchEvent():是否拦截触摸事件

onTouchEvent():处理触摸事件

layoutDependsOn():确定使用BehaviorView要依赖的View的类型

onDependentViewChanged():当被依赖的View状态改变时回调

onDependentViewRemoved():当被依赖的View移除时回调

onMeasureChild():测量使用BehaviorView尺寸

onLayoutChild():确定使用BehaviorView位置

onStartNestedScroll():嵌套滑动开始(ACTION_DOWN),确定Behavior是否要监听此次事件

onStopNestedScroll():嵌套滑动结束(ACTION_UPACTION_CANCEL

onNestedScroll():嵌套滑动进行中,要监听的子 View的滑动事件已经被消费

onNestedPreScroll():嵌套滑动进行中,要监听的子 View将要滑动,滑动事件即将被消费(但最终被谁消费,可以通过代码控制)

onNestedFling():要监听的子 View在快速滑动中

onNestedPreFling():要监听的子View即将快速滑动

具体我们的CoordinatorLayout是怎么让Behavior生效的。那就进入我们后面具体流程的分析吧。

二、具体分析

1、创建对象

既然是分析流程嘛,肯定就从构造函数开始了。

public CoordinatorLayout(Context context) {
          this(context, null);
      }
  ​
      public CoordinatorLayout(Context context, AttributeSet attrs) {
          this(context, attrs, 0);
      }
  ​
      public CoordinatorLayout(Context context, AttributeSet attrs, int defStyleAttr) {
          super(context, attrs, defStyleAttr);
  ​
          ThemeUtils.checkAppCompatTheme(context);
  ​
          final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CoordinatorLayout,
                  defStyleAttr, R.style.Widget_Design_CoordinatorLayout);
          final int keylineArrayRes = a.getResourceId(R.styleable.CoordinatorLayout_keylines, 0);
          if (keylineArrayRes != 0) {
              final Resources res = context.getResources();
              mKeylines = res.getIntArray(keylineArrayRes);
              final float density = res.getDisplayMetrics().density;
              final int count = mKeylines.length;
              for (int i = 0; i < count; i++) {
                  mKeylines[i] = (int) (mKeylines[i] * density);
              }
          }
          mStatusBarBackground = a.getDrawable(R.styleable.CoordinatorLayout_statusBarBackground);
          a.recycle();
  ​
          setupForInsets();
          super.setOnHierarchyChangeListener(new HierarchyChangeListener());
      }

其实构造函数里主要就是初始化了mKeylines变量,然后添加了HierarchyChangeListener监听,其他也没做啥了。

既然构造函数里没有先关的东西,考虑到View的布局的关键流程是从onMeasure()onLayout(),我们也按照这个流程一步步分析咯。

2、布局流程

先看onMeasure()方法。

@Override
      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
          // 准备工作
          prepareChildren();
          // 根据情况添加或者移除OnPreDrawListener
          ensurePreDrawListener();
  ​
          final int paddingLeft = getPaddingLeft();
          final int paddingTop = getPaddingTop();
          final int paddingRight = getPaddingRight();
          final int paddingBottom = getPaddingBottom();
          final int layoutDirection = ViewCompat.getLayoutDirection(this);
          final boolean isRtl = layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL;
          final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
          final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
          final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
          final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
  ​
          final int widthPadding = paddingLeft + paddingRight;
          final int heightPadding = paddingTop + paddingBottom;
          int widthUsed = getSuggestedMinimumWidth();
          int heightUsed = getSuggestedMinimumHeight();
          int childState = 0;
  ​
          final boolean applyInsets = mLastInsets != null && ViewCompat.getFitsSystemWindows(this);
  ​
          final int childCount = mDependencySortedChildren.size();
          for (int i = 0; i < childCount; i++) {
              // 从排好续的集合中依次获取Child View
              final View child = mDependencySortedChildren.get(i);
              if (child.getVisibility() == GONE) {
                  // If the child is GONE, skip...
                  continue;
              }
  ​
              final LayoutParams lp = (LayoutParams) child.getLayoutParams();
  ​
              ……
  ​
              int childWidthMeasureSpec = widthMeasureSpec;
              int childHeightMeasureSpec = heightMeasureSpec;
              // 处理fitsSystemWindows属性
              if (applyInsets && !ViewCompat.getFitsSystemWindows(child)) {
                  // We're set to handle insets but this child isn't, so we will measure the
                  // child as if there are no insets
                  final int horizInsets = mLastInsets.getSystemWindowInsetLeft()
                          + mLastInsets.getSystemWindowInsetRight();
                  final int vertInsets = mLastInsets.getSystemWindowInsetTop()
                          + mLastInsets.getSystemWindowInsetBottom();
  ​
                  childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                          widthSize - horizInsets, widthMode);
                  childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                          heightSize - vertInsets, heightMode);
              }
  ​
              // Behavior相关处理
              final Behavior b = lp.getBehavior();
              // 调用了Behavior的onMeasureChild()方法
              if (b == null || !b.onMeasureChild(this, child, childWidthMeasureSpec, keylineWidthUsed, childHeightMeasureSpec, 0)) { // ①
                  // 如果Behavior里面没有处理,就调用自己的onMeasureChild()方法进行测量
                  onMeasureChild(child, childWidthMeasureSpec, keylineWidthUsed,
                          childHeightMeasureSpec, 0);
              }
  ​
              ……
          }
  ​
          final int width = View.resolveSizeAndState(widthUsed, widthMeasureSpec,
                  childState & View.MEASURED_STATE_MASK);
          final int height = View.resolveSizeAndState(heightUsed, heightMeasureSpec,
                  childState << View.MEASURED_HEIGHT_STATE_SHIFT);
          // 最后将width和height设置好
          setMeasuredDimension(width, height);
      }

里面删除了一些无关的代码,说明已经在注释里面有了。我们可以看到在注释①的地方会调用Behavior.onMeasureChild(),如果我们在该方法里面返回了true的话,就说明我们需要自己测量,CoordinatorLayout就不会再进行测量,如果没有处理,它就会自己测量。

这里最开始调用了prepareChildren();ensurePreDrawListener();两个方法进行预处理。处理什么呢,我们进入看下:

private void prepareChildren() {
          //先清空
          mDependencySortedChildren.clear();
          mChildDag.clear();
  ​
          for (int i = 0, count = getChildCount(); i < count; i++) {
              final View view = getChildAt(i);
  ​
              final LayoutParams lp = getResolvedLayoutParams(view);
              lp.findAnchorView(this, view);
  ​
              // 添加进集合 后边排序用
              mChildDag.addNode(view);
  ​
              // Now iterate again over the other children, adding any dependencies to the graph
              for (int j = 0; j < count; j++) {
                  if (j == i) {
                      continue;
                  }
                  final View other = getChildAt(j);
                  // 判断是否有依赖
                  if (lp.dependsOn(this, view, other)) { // ①
                      if (!mChildDag.contains(other)) {
                          // Make sure that the other node is added
                          mChildDag.addNode(other);
                      }
                      // Now add the dependency to the graph
                      mChildDag.addEdge(other, view);
                  }
              }
          }
  ​
          // 将排好续的子View集合添加到mDependencySortedChildren
          mDependencySortedChildren.addAll(mChildDag.getSortedList()); // ②
          
          // 倒置排序
          Collections.reverse(mDependencySortedChildren); // ③
      }

在注释①的地方,调用了LayoutParams.dependsOn()方法进行依赖判断,内部会调用Behavior.layoutDependsOn()进行判断。这就是我们如果要让两个View产生依赖关系,需要重写该方法返回true的原因了。

注释②的地方,mChildDag.getSortedList()会根据DFS算法,根据子View的依赖关系进行排序(依赖越深的排在前面,没有依赖的在后面)。最后在注释③的地方,进行倒置排序,也就是没有依赖的在前面,依赖越多的在后面去了。

这个方法分析完了,接着看ensurePreDrawListener();

void ensurePreDrawListener() {
          boolean hasDependencies = false;
          final int childCount = getChildCount();
          for (int i = 0; i < childCount; i++) {
              final View child = getChildAt(i);
              if (hasDependencies(child)) {
                  hasDependencies = true;
                  break;
              }
          }
  ​
          if (hasDependencies != mNeedsPreDrawListener) {
              if (hasDependencies) {
                  addPreDrawListener();
              } else {
                  removePreDrawListener();
              }
          }
      }

这里起始就是根据条件,如果子View有依赖关系,并且没有添加监听,那就添加OnPreDrawListener。如果没有依赖关系,但是已经添加了,就移除。那添加这个作用是啥呢?

那我们就需要看下在这个接口回调里面干了啥

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

里面就调用了onChildViewsChanged(EVENT_PRE_DRAW);这个方法。继续跟踪

final void onChildViewsChanged(@DispatchChangeEvent final int type) {
          final int layoutDirection = ViewCompat.getLayoutDirection(this);
          final int childCount = mDependencySortedChildren.size();
          final Rect inset = acquireTempRect();
          final Rect drawRect = acquireTempRect();
          final Rect lastDrawRect = acquireTempRect();
  ​
          for (int i = 0; i < childCount; i++) {
              final View child = mDependencySortedChildren.get(i);
              final LayoutParams lp = (LayoutParams) child.getLayoutParams();
              if (type == EVENT_PRE_DRAW && child.getVisibility() == View.GONE) {
                  // GONE 的控件就不用管了
                  continue;
              }
  ​
              // 获取Child当前的视图位置
              getChildRect(child, true, drawRect);
  ​
              ……
  ​
              if (type != EVENT_VIEW_REMOVED) {
                  getLastChildRect(child, lastDrawRect);
                  // 如果不是移除事件 视图位置、大小等信息没有变化 就忽略
                  if (lastDrawRect.equals(drawRect)) {
                      continue;
                  }
                  recordLastChildRect(child, drawRect);
              }
              
              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();
                  // 先判断是否有依赖关系
                  if (b != null && b.layoutDependsOn(this, checkChild, child)) {
                      // type 为EVENT_PRE_DRAW并且已经通过滑动处理 就不要处理了
                      if (type == EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) {
                          checkLp.resetChangedAfterNestedScroll();
                          continue;
                      }
  ​
                      final boolean handled;
                      switch (type) {
                          case EVENT_VIEW_REMOVED:
                              // 如果是移除事件 回调Behavior.onDependentViewRemoved()
                              b.onDependentViewRemoved(this, checkChild, child);
                              handled = true;
                              break;
                          default:
                              // 其他事件 回调Behavior.onDependentViewChanged()
                              handled = b.onDependentViewChanged(this, checkChild, child);
                              break;
                      }
  ​
                      if (type == EVENT_NESTED_SCROLL) {
                          // If this is from a nested scroll, set the flag so that we may skip
                          // any resulting onPreDraw dispatch (if needed)
                          checkLp.setChangedAfterNestedScroll(handled);
                      }
                  }
              }
          }
          // 释放资源
          releaseTempRect(inset);
          releaseTempRect(drawRect);
          releaseTempRect(lastDrawRect);
      }

看完这个方法,发现,这个方法里面就是处理有依赖的时候,并且被依赖的View的属性发送变化的时候,进入Behavior相应的回调方法。具体分析就请看方法注释吧。

到这里,我们的onMeasure()方法就算是分析完成了,里面主要做了以下几件事情:

1、判断Children是否有依赖关系

2、根据依赖关系将Children进行放入一个排好序的集合中,方便后续使用。

3、根据需要注册OnPreDrawListener,方便在需要的时候,通知被依赖的子View所依赖的View属性发生了变化

4、根据Behavior.onMeasureChild(),判断是否需要自己对Child进行测量。

接下里继续跟着布局流程看下onLayout()方法吧。

  @Override
      protected void onLayout(boolean changed, int l, int t, int r, int b) {
          final int layoutDirection = ViewCompat.getLayoutDirection(this);
          final int childCount = mDependencySortedChildren.size();
          for (int i = 0; i < childCount; i++) {
              final View child = mDependencySortedChildren.get(i);
              if (child.getVisibility() == GONE) {
                  // If the child is GONE, skip...
                  continue;
              }
  ​
              final LayoutParams lp = (LayoutParams) child.getLayoutParams();
              final Behavior behavior = lp.getBehavior();
  ​
  ​
              if (behavior == null || !behavior.onLayoutChild(this, child, layoutDirection)) {// ①
                  onLayoutChild(child, layoutDirection);
              }
          }
      }

这个方法比较简单,就是在注释①的地方,根据Behavior.onLayoutChild()方法的返回值,判断是否需要自己调用onLayoutChild()来摆放Child。所以如果我们想在Behavior中自己控制Child的摆放的话,只需要在该方法中返回true即可。

再跟着流程,看下,默认情况下CoordinatorLayout是怎么处理Children的摆放的吧。

public void onLayoutChild(View child, int layoutDirection) {
          final LayoutParams lp = (LayoutParams) child.getLayoutParams();
          if (lp.checkAnchorChanged()) {
              throw new IllegalStateException("An anchor may not be changed after CoordinatorLayout"
                      + " measurement begins before layout is complete.");
          }
          if (lp.mAnchorView != null) {
              // 这里设置了Anchor的情况
              layoutChildWithAnchor(child, lp.mAnchorView, layoutDirection);
          } else if (lp.keyline >= 0) {
              // 设置了KeyLines的情况
              layoutChildWithKeyline(child, lp.keyline, layoutDirection);
          } else {
              // 默认走这里
              layoutChild(child, layoutDirection);
          }
      }

由于Anchor和KeyLine实际很少用到,这里就不做分析了,感兴趣的朋友可以自己研究研究。继续看layoutChild(child, layoutDirection);

private void layoutChild(View child, int layoutDirection) {
      final LayoutParams lp = (LayoutParams) child.getLayoutParams();
      final Rect parent = acquireTempRect();
      parent.set(getPaddingLeft() + lp.leftMargin,
              getPaddingTop() + lp.topMargin,
              getWidth() - getPaddingRight() - lp.rightMargin,
              getHeight() - getPaddingBottom() - lp.bottomMargin);
  ​
      if (mLastInsets != null && ViewCompat.getFitsSystemWindows(this)
              && !ViewCompat.getFitsSystemWindows(child)) {
          // If we're set to handle insets but this child isn't, then it has been measured as
          // if there are no insets. We need to lay it out to match.
          parent.left += mLastInsets.getSystemWindowInsetLeft();
          parent.top += mLastInsets.getSystemWindowInsetTop();
          parent.right -= mLastInsets.getSystemWindowInsetRight();
          parent.bottom -= mLastInsets.getSystemWindowInsetBottom();
      }
  ​
      final Rect out = acquireTempRect();
      GravityCompat.apply(resolveGravity(lp.gravity), child.getMeasuredWidth(),
              child.getMeasuredHeight(), parent, out, layoutDirection);
      child.layout(out.left, out.top, out.right, out.bottom);
  ​
      releaseTempRect(parent);
      releaseTempRect(out);
  }

这里就是通过调用child.layout(out.left, out.top, out.right, out.bottom);进行布局了。

通过这里也可以看到默认没有其他设置的情况,CoordinatorLayout的布局类似于FrameLayout

到这里,测量布局的流程就算是分析完了。

另外提一点,就是在构造方法里面设置的HierarchyChangeListener监听,再回调里面如果是Child移除了,也会调用onChildViewsChanged()进行处理。

从布局流程,我们大概知道了Behavior中的layoutDependsOn()onDependentViewChanged()onDependentViewRemoved()onMeasureChildonLayoutChild()这几个方法的调用逻辑。剩下还有几个事件处理和嵌套滑动相关的方法调用逻辑呢。先说触摸事件处理,既然是触摸事件,那肯定就是从onInterceptTouchEvent()onTouchEvent()方法入手了。

3、触摸事件

先来看onInterceptTouchEvent()

@Override
      public boolean onInterceptTouchEvent(MotionEvent ev) {
          MotionEvent cancelEvent = null;
  ​
          final int action = ev.getActionMasked();
  ​
          // 重置响应的Behavoir
          if (action == MotionEvent.ACTION_DOWN) {
              resetTouchBehaviors(true);
          }
  ​
          // 具体处理逻辑就在performIntercept()里面了
          final boolean intercepted = performIntercept(ev, TYPE_ON_INTERCEPT);
  ​
          if (cancelEvent != null) {
              cancelEvent.recycle();
          }
  ​
          // 重置响应的Behavoir
          if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
              resetTouchBehaviors(true);
          }
  ​
          return intercepted;
      }

这里在处理前先重置Behavoir,以免上次的Touch事件遗留的东西有干扰。然后通过performIntercept()进行处理,同时在cancel或者up的时候,也要进行Behavior的重置。继续看performIntercept()

private boolean performIntercept(MotionEvent ev, final int type) {
          boolean intercepted = false;
          boolean newBlock = false;
  ​
          MotionEvent cancelEvent = null;
  ​
          final int action = ev.getActionMasked();
  ​
          final List<View> topmostChildList = mTempList1;
          // 先对ChildView进行排序,决定优先处理权
          // API>=21时,使用elevation由低到高排列View;API<21时,按View添加顺序排列
          getTopSortedChildren(topmostChildList);
  ​
          // Let topmost child views inspect first
          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();
  ​
              // 如果前面的View已经拦截 取消后面的
              if ((intercepted || newBlock) && action != MotionEvent.ACTION_DOWN) {
                  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的方法
              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;
                  }
              }
  ​
              // 如果有Child要求阻塞,则就不要处理后续的事件了
              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;
      }

可以看到这个里面,先对ChildView进行排序(越上面的View的优先处理权越高)。然后遍历ChildView,分发给对应的Behavior进行处理,并根据返回值判断是否需要下一个ChildView来处理。

看完onInterceptTouchEvent(),再看onTouchEvent()

public boolean onTouchEvent(MotionEvent ev) {
          boolean handled = false;
          boolean cancelSuper = false;
          MotionEvent cancelEvent = null;
  ​
          final int action = ev.getActionMasked();
  ​
          // 交给performIntercept处理。
          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) {
                  handled = b.onTouchEvent(this, mBehaviorTouchView, ev);
              }
          }
          ……
          if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
              resetTouchBehaviors(false);
          }
  ​
          return handled;
      }

可以看到,内部还是给performIntercept()方法处理了,上面已经分析过了。

对于触摸事件,我们可以看到,起始CoordinatorLayout本身自己没有进行处理,主要就是根据需要分发给Behavior进行代理处理的。

4、嵌套滚动

然后就是最后剩下的嵌套滑动了。这个是怎么处理的呢。我们再回过头来看下CoordinatorLayout的继承关系。

public class CoordinatorLayout extends ViewGroup implements NestedScrollingParent2 {
      ……
  }

看到了吧,CoordinatorLayout实现了NestedScrollingParent2接口。那它在嵌套滚动的机制中,就担任了Parent的角色了。关于嵌套滚动这一块,如果不是很清楚的请看这篇文章,这里就不进行阐述了。只需要知道,如果NestedScrollingParent(这里的NestedScrollingParent2继承自NestedScrollingParent)类型的ViewGroup里面有NestedScrollingChild类型的子View(如RecyclerView,NestedScrollView),那子View滑动的时候,就会触发嵌套滑动机制,然后进入Parent相应的方法里面。所以这里,我们就看下CoordinatorLayout对该接口的实现方法吧。

@Override
      public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
          return onStartNestedScroll(child, target, nestedScrollAxes, ViewCompat.TYPE_TOUCH);
      }
  ​
      @Override
      public boolean onStartNestedScroll(View child, View target, int axes, int type) {
          boolean handled = false;
  ​
          final int childCount = getChildCount();
          for (int i = 0; i < childCount; i++) {
              final View view = getChildAt(i);
              // 剔除掉页面没有展示的View
              if (view.getVisibility() == View.GONE) {
                  // If it's GONE, don't dispatch
                  continue;
              }
              final LayoutParams lp = (LayoutParams) view.getLayoutParams();
              final Behavior viewBehavior = lp.getBehavior();
              if (viewBehavior != null) {
                  // 交给Behavior处理
                  final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child,
                          target, axes, type);
                  handled |= accepted;
                  lp.setNestedScrollAccepted(type, accepted);
              } else {
                  lp.setNestedScrollAccepted(type, false);
              }
          }
          return handled;
      }
  ​
      @Override
      public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
          onNestedScrollAccepted(child, target, nestedScrollAxes, ViewCompat.TYPE_TOUCH);
      }
  ​
      @Override
      public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes, int type) {
          mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes, type);
          mNestedScrollingTarget = target;
  ​
          final int childCount = getChildCount();
          for (int i = 0; i < childCount; i++) {
              final View view = getChildAt(i);
              final LayoutParams lp = (LayoutParams) view.getLayoutParams();
              if (!lp.isNestedScrollAccepted(type)) {
                  continue;
              }
  ​
              final Behavior viewBehavior = lp.getBehavior();
              if (viewBehavior != null) {
                  // 交给Behavior处理
                  viewBehavior.onNestedScrollAccepted(this, view, child, target,
                          nestedScrollAxes, type);
              }
          }
      }
  ​
      @Override
      public void onStopNestedScroll(View target) {
          onStopNestedScroll(target, ViewCompat.TYPE_TOUCH);
      }
  ​
      @Override
      public void onStopNestedScroll(View target, int type) {
          mNestedScrollingParentHelper.onStopNestedScroll(target, type);
  ​
          final int childCount = getChildCount();
          for (int i = 0; i < childCount; i++) {
              final View view = getChildAt(i);
              final LayoutParams lp = (LayoutParams) view.getLayoutParams();
              if (!lp.isNestedScrollAccepted(type)) {
                  continue;
              }
  ​
              final Behavior viewBehavior = lp.getBehavior();
              if (viewBehavior != null) {
                  // 交给Behavior处理
                  viewBehavior.onStopNestedScroll(this, view, target, type);
              }
              lp.resetNestedScroll(type);
              lp.resetChangedAfterNestedScroll();
          }
          mNestedScrollingTarget = null;
      }
  ​
      @Override
      public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
              int dxUnconsumed, int dyUnconsumed) {
          onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
                  ViewCompat.TYPE_TOUCH);
      }
  ​
      @Override
      public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
              int dxUnconsumed, int dyUnconsumed, int type) {
          final int childCount = getChildCount();
          boolean accepted = false;
  ​
          for (int i = 0; i < childCount; i++) {
              final View view = getChildAt(i);
              if (view.getVisibility() == GONE) {
                  // If the child is GONE, skip...
                  continue;
              }
  ​
              final LayoutParams lp = (LayoutParams) view.getLayoutParams();
              if (!lp.isNestedScrollAccepted(type)) {
                  continue;
              }
  ​
              final Behavior viewBehavior = lp.getBehavior();
              if (viewBehavior != null) {
                  // 交给Behavior处理
                  viewBehavior.onNestedScroll(this, view, target, dxConsumed, dyConsumed,
                          dxUnconsumed, dyUnconsumed, type);
                  accepted = true;
              }
          }
  ​
          if (accepted) {
              onChildViewsChanged(EVENT_NESTED_SCROLL);
          }
      }
  ​
      @Override
      public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
          onNestedPreScroll(target, dx, dy, consumed, ViewCompat.TYPE_TOUCH);
      }
  ​
      @Override
      public void onNestedPreScroll(View target, int dx, int dy, int[] consumed, int  type) {
          int xConsumed = 0;
          int yConsumed = 0;
          boolean accepted = false;
  ​
          final int childCount = getChildCount();
          for (int i = 0; i < childCount; i++) {
              final View view = getChildAt(i);
              if (view.getVisibility() == GONE) {
                  // If the child is GONE, skip...
                  continue;
              }
  ​
              final LayoutParams lp = (LayoutParams) view.getLayoutParams();
              if (!lp.isNestedScrollAccepted(type)) {
                  continue;
              }
  ​
              final Behavior viewBehavior = lp.getBehavior();
              if (viewBehavior != null) {
                  mTempIntPair[0] = mTempIntPair[1] = 0;
                  // 交给Behavior处理
                  viewBehavior.onNestedPreScroll(this, view, target, dx, dy, mTempIntPair, type);
  ​
                  xConsumed = dx > 0 ? Math.max(xConsumed, mTempIntPair[0])
                          : Math.min(xConsumed, mTempIntPair[0]);
                  yConsumed = dy > 0 ? Math.max(yConsumed, mTempIntPair[1])
                          : Math.min(yConsumed, mTempIntPair[1]);
  ​
                  accepted = true;
              }
          }
  ​
          consumed[0] = xConsumed;
          consumed[1] = yConsumed;
  ​
          if (accepted) {
              // 通知ChildView布局改变了
              onChildViewsChanged(EVENT_NESTED_SCROLL);
          }
      }
  ​
      @Override
      public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
          boolean handled = false;
  ​
          final int childCount = getChildCount();
          for (int i = 0; i < childCount; i++) {
              final View view = getChildAt(i);
              if (view.getVisibility() == GONE) {
                  // If the child is GONE, skip...
                  continue;
              }
  ​
              final LayoutParams lp = (LayoutParams) view.getLayoutParams();
              if (!lp.isNestedScrollAccepted(ViewCompat.TYPE_TOUCH)) {
                  continue;
              }
  ​
              final Behavior viewBehavior = lp.getBehavior();
              if (viewBehavior != null) {
                  // 交给Behavior处理
                  handled |= viewBehavior.onNestedFling(this, view, target, velocityX, velocityY,
                          consumed);
              }
          }
          if (handled) {
              // 通知ChildView改变了
              onChildViewsChanged(EVENT_NESTED_SCROLL);
          }
          return handled;
      }
  ​
      @Override
      public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
          boolean handled = false;
  ​
          final int childCount = getChildCount();
          for (int i = 0; i < childCount; i++) {
              final View view = getChildAt(i);
              if (view.getVisibility() == GONE) {
                  // If the child is GONE, skip...
                  continue;
              }
  ​
              final LayoutParams lp = (LayoutParams) view.getLayoutParams();
              if (!lp.isNestedScrollAccepted(ViewCompat.TYPE_TOUCH)) {
                  continue;
              }
  ​
              final Behavior viewBehavior = lp.getBehavior();
              if (viewBehavior != null) {
                  // 交给Behavior处理
                  handled |= viewBehavior.onNestedPreFling(this, view, target, velocityX, velocityY);
              }
          }
          return handled;
      }
  ​
      @Override
      public int getNestedScrollAxes() {
          return mNestedScrollingParentHelper.getNestedScrollAxes();
      }

如果就是嵌套滑动机制实现,不清楚的请看前面的文章链接,这里就不赘述了。总体上就是交给给Behavior对应的方法进行处理,再根据实际情况,调用onChildViewsChanged()通知ChildView位置发生了变化。

三、总结

到此,我们CoordinatorLayout的源码分析就算结束了,当然里面还有很多没有分析到的内容,主要是平时用的也不多,就没有重点介绍,感兴趣的朋友就自己研究下咯。按照惯例,还是做一个简单总结吧。

对于CoordinatorLayout的使用,除了Google给我们写好的几个类,主要就是自定义Behavior了,所以本篇文章以Behavior几个比较常用的方法为中心,然后分类进行调用逻辑的分析。

1、布局相关

先是在构造方法中注册HierarchyChangeListener监听ChildView的移除,从而调用Behavior.onDependentViewRemoved()

CoordinatorLayout会在onMeasure()方法中,对ChildView根据依赖关系进行排序的同时调用Behavior.layoutDependsOn(),然后注册OnPreDrawListener监听ChildView的属性状态的改变以便Behavior.onDependentViewChanged()的调用。紧接着调用Behavior.onMeasureChild()

onLayout()方法中,调用Behavior.onLayoutChild()方法

2、触摸事件

分别在onInterceptTouchEvent()onTouchEvent()中,通过调用performIntercept()方法,去执行Behavior.onInterceptTouchEvent()Behavior.onTouchEvent()

3、嵌套滚动

这个就是在CoordinatorLayout实现NestedScrollingParent2接口的方法中,去调用Behavior对应的嵌套滑动的方法。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值