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屬性 用途
AndchorId layout_anchor&layout_anchorGravity 佈局時根據自身gravitylayout_anchorGravity放置在被anchor的View中
Behavior layout_behavior 輔助Coordinator對View進行layout、nestedScroll的處理
KeyLine layout_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對應的嵌套滑動的方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章