[关闭]
@xujun94 2017-05-11T22:32:34.000000Z 字数 22667 阅读 1380

一步步带你读懂 CoordinatorLayout 源码

使用CoordinatorLayout打造各种炫酷的效果

自定义Behavior —— 仿知乎,FloatActionButton隐藏与展示

NestedScrolling 机制深入解析

一步步带你读懂 CoordinatorLayout 源码

前言

记得在去年的时候,就写过一篇博客使用CoordinatorLayout打造各种炫酷的效果,里面介绍了 CoordinatorLayout 的常用用法,今天,这篇博客将带大家一步步来分析源码。

CoordinatorLayout 实现了 NestedScrollingParent 接口,是一个容器,我们可以通过指定 behavior 来实现各种各样炫酷的效果。需要注意的是本篇博客分析的版本是 25.2.0 的。

分析之前,先放一下效果图,给大家看一看,增加一下博客访问量,下一篇博客会教大家如何实现。

仿新浪微博效果图

Behavior 的初始化

Behavior 是 CoordinatorLayout 里面的一个静态类。重写里面的若干方法,我们可以实现各种炫酷的效果,比如仿 UC 主页,仿新浪微博,仿 QQ 浏览器主页,仿知乎首页等效果。

Behavior 的主要几个方法

  1. public static abstract class Behavior<V extends View> {
  2. // 中间省略了若干方法
  3. public Behavior() {
  4. }
  5. public Behavior(Context context, AttributeSet attrs) {
  6. }
  7. public void onAttachedToLayoutParams(@NonNull CoordinatorLayout.LayoutParams params) {
  8. }
  9. public void onDetachedFromLayoutParams() {
  10. }
  11. public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
  12. return false;
  13. }
  14. public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
  15. return false;
  16. }
  17. public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) {
  18. return false;
  19. }
  20. public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
  21. return false;
  22. }
  23. public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency) {
  24. }
  25. public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,
  26. V child, View directTargetChild, View target, int nestedScrollAxes) {
  27. return false;
  28. }
  29. public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, V child,
  30. View directTargetChild, View target, int nestedScrollAxes) {
  31. // Do nothing
  32. }
  33. public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) {
  34. // Do nothing
  35. }
  36. public void onNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target,
  37. int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
  38. // Do nothing
  39. }
  40. public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target,
  41. int dx, int dy, int[] consumed) {
  42. // Do nothing
  43. }
  44. public boolean onNestedFling(CoordinatorLayout coordinatorLayout, V child, View target,
  45. float velocityX, float velocityY, boolean consumed) {
  46. return false;
  47. }
  48. public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, V child, View target,
  49. float velocityX, float velocityY) {
  50. return false;
  51. }
  52. }

Behavior 的初始化时机

这种方式指定的 Behavior 是在生成 layoutParams 里面进行转化的。

  1. LayoutParams(Context context, AttributeSet attrs) {
  2. super(context, attrs);
  3. if (mBehaviorResolved) {
  4. mBehavior = parseBehavior(context, attrs, a.getString(
  5. R.styleable.CoordinatorLayout_Layout_layout_behavior));
  6. }
  7. a.recycle();
  8. if (mBehavior != null) {
  9. // If we have a Behavior, dispatch that it has been attached
  10. mBehavior.onAttachedToLayoutParams(this);
  11. }
  12. }
  1. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  2. prepareChildren();
  3. }
  4. private void prepareChildren() {
  5. mDependencySortedChildren.clear();
  6. mChildDag.clear();
  7. for (int i = 0, count = getChildCount(); i < count; i++) {
  8. final View view = getChildAt(i);
  9. final LayoutParams lp = getResolvedLayoutParams(view);
  10. lp.findAnchorView(this, view);
  11. mChildDag.addNode(view);
  12. // Now iterate again over the other children, adding any dependencies to the graph
  13. for (int j = 0; j < count; j++) {
  14. if (j == i) {
  15. continue;
  16. }
  17. final View other = getChildAt(j);
  18. final LayoutParams otherLp = getResolvedLayoutParams(other);
  19. if (otherLp.dependsOn(this, other, view)) {
  20. if (!mChildDag.contains(other)) {
  21. // Make sure that the other node is added
  22. mChildDag.addNode(other);
  23. }
  24. // Now add the dependency to the graph
  25. mChildDag.addEdge(view, other);
  26. }
  27. }
  28. }
  29. // Finally add the sorted graph list to our list
  30. mDependencySortedChildren.addAll(mChildDag.getSortedList());
  31. // We also need to reverse the result since we want the start of the list to contain
  32. // Views which have no dependencies, then dependent views after that
  33. Collections.reverse(mDependencySortedChildren);
  34. }
  35. LayoutParams getResolvedLayoutParams(View child) {
  36. final LayoutParams result = (LayoutParams) child.getLayoutParams();
  37. if (!result.mBehaviorResolved) {
  38. Class<?> childClass = child.getClass();
  39. DefaultBehavior defaultBehavior = null;
  40. while (childClass != null &&
  41. (defaultBehavior = childClass.getAnnotation(DefaultBehavior.class)) == null) {
  42. childClass = childClass.getSuperclass();
  43. }
  44. if (defaultBehavior != null) {
  45. try {
  46. result.setBehavior(defaultBehavior.value().newInstance());
  47. } catch (Exception e) {
  48. Log.e(TAG, "Default behavior class " + defaultBehavior.value().getName() +
  49. " could not be instantiated. Did you forget a default constructor?", e);
  50. }
  51. }
  52. result.mBehaviorResolved = true;
  53. }
  54. return result;
  55. }

NestedScrollingParent 与 NestedScrollingChild 接口方法是怎样回调的

执行流程

  1. 在 Action_Down 的时候,Scrolling child 会调用 startNestedScroll 方法,通过 childHelper 回调 Scrolling Parent 的 startNestedScroll 方法
  2. 在 Action_move 的时候,Scrolling Child 要开始滑动的时候,会调用dispatchNestedPreScroll 方法,通过 ChildHelper 询问 Scrolling Parent 是否要先于 Child 进行 滑动,若需要的话,会调用 Parent 的 onNestedPreScroll 方法,协同 Child 一起进行滑动
  3. 当 ScrollingChild 滑动完成的时候,会调用 dispatchNestedScroll 方法,通过 ChildHelper 询问 Scrolling Parent 是否需要进行滑动,需要的话,会 调用 Parent 的 onNestedScroll 方法
  4. 在 Action_down,Action_move 的时候,会调用 Scrolling Child 的stopNestedScroll ,通过 ChildHelper 询问 Scrolling parent 的 stopNestedScroll 方法。
  5. 如果需要处理 Fling 动作,我们可以通过 VelocityTrackerCompat 获得相应的速度,并在 Action_up 的时候,调用 dispatchNestedPreFling 方法,通过 ChildHelper 询问 Parent 是否需要先于 child 进行 Fling 动作
  6. 在 Child 处理完 Fling 动作时候,如果 Scrolling Parent 还需要处理 Fling 动作,我们可以调用 dispatchNestedFling 方法,通过 ChildHelper ,调用 Parent 的 onNestedFling 方法

在上一篇博客NestedScrolling 机制深入解析 的 时候其实已经说到:是通过 NestedScrollingChildHelper 来完成的,具体 的请看里面分分析,这里就不再一一阐述。

Behavior 方法与 NestedScrollingParent 方法之间的关系

我们知道 NestedScrollingParent 主要有这些方法,

在 Behavior 方法里面也有这些方法,与 NestedScrollingParent 方法 几乎也是一一对应的。在 CoordinatorLayout 里面。NestedScrollingParent 接口的方法的具体 实现逻辑 都会交给 Behavior 对应的方法去处理。下面我们一起来看一下是怎样处理的。

onStartNestedScroll 方法

思想大概是这样的:

遍历所有的孩子 ,如果可见性是 GONE,跳过。如果可见性不是 GONE,通过 layoutParams 拿到 Behavior,判断 behavior 是否为空,不为空,调用 behavior 的对应方法 onStartNestedScroll 和 acceptNestedScroll 方法。

  1. // 开始滑动的时候
  2. @Override
  3. public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
  4. boolean handled = false;
  5. final int childCount = getChildCount();
  6. for (int i = 0; i < childCount; i++) {
  7. final View view = getChildAt(i);
  8. if (view.getVisibility() == View.GONE) {
  9. // If it's GONE, don't dispatch
  10. continue;
  11. }
  12. // 通过 LayoutParams 拿到对应的 Behavior
  13. final LayoutParams lp = (LayoutParams) view.getLayoutParams();
  14. final Behavior viewBehavior = lp.getBehavior();
  15. if (viewBehavior != null) {
  16. // 交给 Behavior 去处理
  17. final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child, target,
  18. nestedScrollAxes);
  19. handled |= accepted;
  20. lp.acceptNestedScroll(accepted);
  21. } else {
  22. lp.acceptNestedScroll(false);
  23. }
  24. }
  25. return handled;
  26. }

onNestedScrollAccepted 方法

思想大概是这样的

  1. @Override
  2. public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
  3. mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes);
  4. mNestedScrollingDirectChild = child;
  5. mNestedScrollingTarget = target;
  6. final int childCount = getChildCount();
  7. for (int i = 0; i < childCount; i++) {
  8. final View view = getChildAt(i);
  9. final LayoutParams lp = (LayoutParams) view.getLayoutParams();
  10. if (!lp.isNestedScrollAccepted()) {
  11. continue;
  12. }
  13. final Behavior viewBehavior = lp.getBehavior();
  14. if (viewBehavior != null) {
  15. // 调用 behavior 的相应方法
  16. viewBehavior.onNestedScrollAccepted(this, view, child, target, nestedScrollAxes);
  17. }
  18. }
  19. }

onNestedPreScroll 方法

我们知道 onNestedPreScroll 是在 Scrolling child 滑动之前回调的,提供机会给 Scrooling Parent 先于 child 进行滑动的。

在 CoordinatorLayout 里面,它的处理流程是这样的。 遍历所有的孩子,判断可见性是否为 GONE,如果是 ,跳过当前 子 View,通过 LayoutParams 判断是否处理滑动事件,不处理滑动 事件,跳过,拿到 Behavior,判断 Behavior 是否为空,不过空,回调 Behavior 的 onNestedPreScroll 方法。

  1. @Override
  2. public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
  3. int xConsumed = 0;
  4. int yConsumed = 0;
  5. boolean accepted = false;
  6. final int childCount = getChildCount();
  7. for (int i = 0; i < childCount; i++) {
  8. final View view = getChildAt(i);
  9. if (view.getVisibility() == GONE) {
  10. // If the child is GONE, skip...
  11. continue;
  12. }
  13. final LayoutParams lp = (LayoutParams) view.getLayoutParams();
  14. if (!lp.isNestedScrollAccepted()) {
  15. continue;
  16. }
  17. final Behavior viewBehavior = lp.getBehavior();
  18. if (viewBehavior != null) {
  19. mTempIntPair[0] = mTempIntPair[1] = 0;
  20. viewBehavior.onNestedPreScroll(this, view, target, dx, dy, mTempIntPair);
  21. xConsumed = dx > 0 ? Math.max(xConsumed, mTempIntPair[0])
  22. : Math.min(xConsumed, mTempIntPair[0]);
  23. yConsumed = dy > 0 ? Math.max(yConsumed, mTempIntPair[1])
  24. : Math.min(yConsumed, mTempIntPair[1]);
  25. accepted = true;
  26. }
  27. }
  28. consumed[0] = xConsumed;
  29. consumed[1] = yConsumed;
  30. if (accepted) {
  31. onChildViewsChanged(EVENT_NESTED_SCROLL);
  32. }
  33. }

onNestedScroll 方法

在 Scrolling Child 滑动之后,提供机会给 Scrolling Parent 滑动,事件的处理 逻辑就不一一阐述了 ,跟前面的差不多

  1. @Override
  2. public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
  3. int dxUnconsumed, int dyUnconsumed) {
  4. final int childCount = getChildCount();
  5. boolean accepted = false;
  6. for (int i = 0; i < childCount; i++) {
  7. final View view = getChildAt(i);
  8. if (view.getVisibility() == GONE) {
  9. // If the child is GONE, skip...
  10. continue;
  11. }
  12. final LayoutParams lp = (LayoutParams) view.getLayoutParams();
  13. // 如果之前没有处理滑动事件,直接返回,不调用 onStopNestedScroll 方法
  14. if (!lp.isNestedScrollAccepted()) {
  15. continue;
  16. }
  17. final Behavior viewBehavior = lp.getBehavior();
  18. if (viewBehavior != null) {
  19. // 调用 behavior 的相应方法
  20. viewBehavior.onNestedScroll(this, view, target, dxConsumed, dyConsumed,
  21. dxUnconsumed, dyUnconsumed);
  22. accepted = true;
  23. }
  24. }
  25. if (accepted) {
  26. onChildViewsChanged(EVENT_NESTED_SCROLL);
  27. }
  28. }

onNestedPreFling 和 onNestedFling 方法

  1. @Override
  2. public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
  3. boolean handled = false;
  4. final int childCount = getChildCount();
  5. for (int i = 0; i < childCount; i++) {
  6. final View view = getChildAt(i);
  7. if (view.getVisibility() == GONE) {
  8. // If the child is GONE, skip...
  9. continue;
  10. }
  11. final LayoutParams lp = (LayoutParams) view.getLayoutParams();
  12. if (!lp.isNestedScrollAccepted()) {
  13. continue;
  14. }
  15. final Behavior viewBehavior = lp.getBehavior();
  16. if (viewBehavior != null) {
  17. handled |= viewBehavior.onNestedPreFling(this, view, target, velocityX, velocityY);
  18. }
  19. }
  20. return handled;
  21. }
  22. @Override
  23. public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
  24. boolean handled = false;
  25. final int childCount = getChildCount();
  26. for (int i = 0; i < childCount; i++) {
  27. final View view = getChildAt(i);
  28. if (view.getVisibility() == GONE) {
  29. // If the child is GONE, skip...
  30. continue;
  31. }
  32. final LayoutParams lp = (LayoutParams) view.getLayoutParams();
  33. if (!lp.isNestedScrollAccepted()) {
  34. continue;
  35. }
  36. final Behavior viewBehavior = lp.getBehavior();
  37. if (viewBehavior != null) {
  38. handled |= viewBehavior.onNestedFling(this, view, target, velocityX, velocityY,
  39. consumed);
  40. }
  41. }
  42. if (handled) {
  43. onChildViewsChanged(EVENT_NESTED_SCROLL);
  44. }
  45. return handled;
  46. }

onStopNestedScroll 方法

  1. @Override
  2. public void onStopNestedScroll(View target) {
  3. mNestedScrollingParentHelper.onStopNestedScroll(target);
  4. final int childCount = getChildCount();
  5. for (int i = 0; i < childCount; i++) {
  6. final View view = getChildAt(i);
  7. final LayoutParams lp = (LayoutParams) view.getLayoutParams();
  8. // 如果之前没有处理滑动事件,直接返回,不调用 onStopNestedScroll 方法
  9. if (!lp.isNestedScrollAccepted()) {
  10. continue;
  11. }
  12. final Behavior viewBehavior = lp.getBehavior();
  13. if (viewBehavior != null) {
  14. viewBehavior.onStopNestedScroll(this, view, target);
  15. }
  16. lp.resetNestedScroll();
  17. lp.resetChangedAfterNestedScroll();
  18. }
  19. mNestedScrollingDirectChild = null;
  20. mNestedScrollingTarget = null;
  21. }

Behavior 相比 NestedScrollingParent 独有的方法

返回 true,当 dependency 改变的 时候,将会回调 onDependentViewChanged 方法.比如,当我们依赖于 AppBarLayout 的时候,我们可以这样写 。

  1. @Override
  2. public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
  3. // We depend on any AppBarLayouts
  4. return dependency instanceof AppBarLayout;
  5. }

与 layoutDependsOn 息息相关,当 layoutDependsOn 返回TRUE的时候,才会回调这个方法。

CoordinatorLayout 是如何监听 View 的状态的?

那 onDependentViewChanged 和 onDependentViewRemove 这两个方法是如何监听得到 View 变化和移除的?其实是在 onAttachedToWindow 方法里面,他会为 ViewTreeObserver 视图树添加 OnPreDrawListener 监听。

  1. @Override
  2. public void onAttachedToWindow() {
  3. super.onAttachedToWindow();
  4. resetTouchBehaviors();
  5. if (mNeedsPreDrawListener) {
  6. if (mOnPreDrawListener == null) {
  7. mOnPreDrawListener = new OnPreDrawListener();
  8. }
  9. final ViewTreeObserver vto = getViewTreeObserver();
  10. vto.addOnPreDrawListener(mOnPreDrawListener);
  11. }
  12. if (mLastInsets == null && ViewCompat.getFitsSystemWindows(this)) {
  13. // We're set to fitSystemWindows but we haven't had any insets yet...
  14. // We should request a new dispatch of window insets
  15. ViewCompat.requestApplyInsets(this);
  16. }
  17. mIsAttachedToWindow = true;
  18. }
  19. class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
  20. @Override
  21. public boolean onPreDraw() {
  22. onChildViewsChanged(EVENT_PRE_DRAW);
  23. return true;
  24. }
  25. }

在 OnPreDrawListener 监听里面会调用 onChildViewsChanged 方法,在该方法里面会根据 View的状态回调 onDependentViewRemoved 或者 onDependentViewChanged 方法。

  1. final void onChildViewsChanged(@DispatchChangeEvent final int type) {
  2. ---
  3. / / 省略若干方法
  4. for (int i = 0; i < childCount; i++) {
  5. final View child = mDependencySortedChildren.get(i);
  6. final LayoutParams lp = (LayoutParams) child.getLayoutParams();
  7. if (type == EVENT_PRE_DRAW && child.getVisibility() == View.GONE) {
  8. // Do not try to update GONE child views in pre draw updates.
  9. continue;
  10. }
  11. ---
  12. // Get the current draw rect of the view
  13. ----
  14. / / 省略若干方法
  15. if (type == EVENT_PRE_DRAW) {
  16. // Did it change? if not continue
  17. getLastChildRect(child, lastDrawRect);
  18. if (lastDrawRect.equals(drawRect)) {
  19. continue;
  20. }
  21. recordLastChildRect(child, drawRect);
  22. }
  23. // Update any behavior-dependent views for the change
  24. for (int j = i + 1; j < childCount; j++) {
  25. final View checkChild = mDependencySortedChildren.get(j);
  26. final LayoutParams checkLp = (LayoutParams) checkChild.getLayoutParams();
  27. final Behavior b = checkLp.getBehavior();
  28. if (b != null && b.layoutDependsOn(this, checkChild, child)) {
  29. if (type == EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) {
  30. // If this is from a pre-draw and we have already been changed
  31. // from a nested scroll, skip the dispatch and reset the flag
  32. checkLp.resetChangedAfterNestedScroll();
  33. continue;
  34. }
  35. final boolean handled;
  36. switch (type) {
  37. case EVENT_VIEW_REMOVED:
  38. // EVENT_VIEW_REMOVED means that we need to dispatch
  39. // onDependentViewRemoved() instead
  40. // 回调 Behavior 的 onDependentViewRemoved 方法
  41. b.onDependentViewRemoved(this, checkChild, child);
  42. handled = true;
  43. break;
  44. default:
  45. // Otherwise we dispatch onDependentViewChanged()
  46. // 回调 Behavior 的 onDependentViewChanged 方法
  47. handled = b.onDependentViewChanged(this, checkChild, child);
  48. break;
  49. }
  50. if (type == EVENT_NESTED_SCROLL) {
  51. // If this is from a nested scroll, set the flag so that we may skip
  52. // any resulting onPreDraw dispatch (if needed)
  53. checkLp.setChangedAfterNestedScroll(handled);
  54. }
  55. }
  56. }
  57. }
  58. ---
  59. }

CoordinatorLayout 如何移除 View 的监听状态的

我们知道当 View 被销毁的时候,会回调 onDetachedFromWindow 这个方法,因此适合在这个方法里面移除 View 视图树的 PreDrawListener 监听。

  1. @Override
  2. public void onDetachedFromWindow() {
  3. super.onDetachedFromWindow();
  4. resetTouchBehaviors();
  5. if (mNeedsPreDrawListener && mOnPreDrawListener != null) {
  6. final ViewTreeObserver vto = getViewTreeObserver();
  7. vto.removeOnPreDrawListener(mOnPreDrawListener);
  8. }
  9. if (mNestedScrollingTarget != null) {
  10. onStopNestedScroll(mNestedScrollingTarget);
  11. }
  12. mIsAttachedToWindow = false;
  13. }

CoordinatorLayout 的 measure 和 layout

measure

  1. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  2. // 处理 child 的一些 相关属性 ,比如 Behavior等
  3. prepareChildren();
  4. // 如果有依赖的话,添加 OnPreDrawListener 监听,没有的话,移除 OnPreDrawListener 监听
  5. ensurePreDrawListener();
  6. ---
  7. // 省略了 染过逻辑,主要是处理 padding的
  8. for (int i = 0; i < childCount; i++) {
  9. final View child = mDependencySortedChildren.get(i);
  10. if (child.getVisibility() == GONE) {
  11. // If the child is GONE, skip...
  12. continue;
  13. }
  14. final LayoutParams lp = (LayoutParams) child.getLayoutParams();
  15. ----
  16. final Behavior b = lp.getBehavior();
  17. // 回调 Behavior 的 onMeasureChild 方法
  18. if (b == null || !b.onMeasureChild(this, child, childWidthMeasureSpec, keylineWidthUsed,
  19. childHeightMeasureSpec, 0)) {
  20. onMeasureChild(child, childWidthMeasureSpec, keylineWidthUsed,
  21. childHeightMeasureSpec, 0);
  22. }
  23. widthUsed = Math.max(widthUsed, widthPadding + child.getMeasuredWidth() +
  24. lp.leftMargin + lp.rightMargin);
  25. heightUsed = Math.max(heightUsed, heightPadding + child.getMeasuredHeight() +
  26. lp.topMargin + lp.bottomMargin);
  27. childState = ViewCompat.combineMeasuredStates(childState,
  28. ViewCompat.getMeasuredState(child));
  29. }
  30. final int width = ViewCompat.resolveSizeAndState(widthUsed, widthMeasureSpec,
  31. childState & ViewCompat.MEASURED_STATE_MASK);
  32. final int height = ViewCompat.resolveSizeAndState(heightUsed, heightMeasureSpec,
  33. childState << ViewCompat.MEASURED_HEIGHT_STATE_SHIFT);
  34. setMeasuredDimension(width, height);
  35. }

我们进入 prepareChildren 方法里面,可以发现它对 CoordinatorLayout 里面的子 View 进行了排序,排序的结果是 最后被依赖的 View 会排在最前面。举个例子 A 依赖于 B,那么 B会排在前面,A 会排在 B 的 后面。这样的排序结果是合理的,因为 A 既然依赖于 B,那么 B 肯定要有限 measure。

  1. private void prepareChildren() {
  2. mDependencySortedChildren.clear();
  3. mChildDag.clear();
  4. for (int i = 0, count = getChildCount(); i < count; i++) {
  5. final View view = getChildAt(i);
  6. final LayoutParams lp = getResolvedLayoutParams(view);
  7. lp.findAnchorView(this, view);
  8. mChildDag.addNode(view);
  9. // Now iterate again over the other children, adding any dependencies to the graph
  10. for (int j = 0; j < count; j++) {
  11. if (j == i) {
  12. continue;
  13. }
  14. final View other = getChildAt(j);
  15. final LayoutParams otherLp = getResolvedLayoutParams(other);
  16. if (otherLp.dependsOn(this, other, view)) {
  17. if (!mChildDag.contains(other)) {
  18. // Make sure that the other node is added
  19. mChildDag.addNode(other);
  20. }
  21. // Now add the dependency to the graph
  22. mChildDag.addEdge(view, other);
  23. }
  24. }
  25. }
  26. // Finally add the sorted graph list to our list
  27. mDependencySortedChildren.addAll(mChildDag.getSortedList());
  28. // We also need to reverse the result since we want the start of the list to contain
  29. // Views which have no dependencies, then dependent views after that
  30. Collections.reverse(mDependencySortedChildren);
  31. }

接下来 我们进入 ensurePreDrawListener 方法里面,看看里面到底做了什么?

  1. void ensurePreDrawListener() {
  2. boolean hasDependencies = false;
  3. final int childCount = getChildCount();
  4. for (int i = 0; i < childCount; i++) {
  5. final View child = getChildAt(i);
  6. if (hasDependencies(child)) {
  7. hasDependencies = true;
  8. break;
  9. }
  10. }
  11. if (hasDependencies != mNeedsPreDrawListener) {
  12. if (hasDependencies) {
  13. addPreDrawListener();
  14. } else {
  15. removePreDrawListener();
  16. }
  17. }
  18. }

其实它所做的工作就是 判断 子View ,如果有依赖的话,添加 OnPreDrawListener 监听,没有的话,移除 OnPreDrawListener 监听。

那这个 OnPreDrawListener 监听是干什么用的呢?

  1. void addPreDrawListener() {
  2. if (mIsAttachedToWindow) {
  3. // Add the listener
  4. if (mOnPreDrawListener == null) {
  5. mOnPreDrawListener = new OnPreDrawListener();
  6. }
  7. final ViewTreeObserver vto = getViewTreeObserver();
  8. vto.addOnPreDrawListener(mOnPreDrawListener);
  9. }
  10. // Record that we need the listener regardless of whether or not we're attached.
  11. // We'll add the real listener when we become attached.
  12. mNeedsPreDrawListener = true;
  13. }
  14. class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
  15. @Override
  16. public boolean onPreDraw() {
  17. onChildViewsChanged(EVENT_PRE_DRAW);
  18. return true;
  19. }
  20. }

其实就是当 Child 改变的 时候,回调依赖它的 Behavior 的 onDependentViewChanged 或者 onDependentViewRemoved 方法,从而来调整 View 在界面的显示位置 。

  1. final void onChildViewsChanged(@DispatchChangeEvent final int type) {
  2. ---
  3. for (int i = 0; i < childCount; i++) {
  4. final View child = mDependencySortedChildren.get(i);
  5. final LayoutParams lp = (LayoutParams) child.getLayoutParams();
  6. if (type == EVENT_PRE_DRAW && child.getVisibility() == View.GONE) {
  7. // Do not try to update GONE child views in pre draw updates.
  8. continue;
  9. }
  10. ---
  11. // 省略若干方法
  12. // Dodge inset edges if necessary
  13. if (lp.dodgeInsetEdges != Gravity.NO_GRAVITY && child.getVisibility() == View.VISIBLE) {
  14. offsetChildByInset(child, inset, layoutDirection);
  15. }
  16. if (type == EVENT_PRE_DRAW) {
  17. // Did it change? if not continue
  18. getLastChildRect(child, lastDrawRect);
  19. if (lastDrawRect.equals(drawRect)) {
  20. continue;
  21. }
  22. recordLastChildRect(child, drawRect);
  23. }
  24. // Update any behavior-dependent views for the change
  25. for (int j = i + 1; j < childCount; j++) {
  26. final View checkChild = mDependencySortedChildren.get(j);
  27. final LayoutParams checkLp = (LayoutParams) checkChild.getLayoutParams();
  28. final Behavior b = checkLp.getBehavior();
  29. if (b != null && b.layoutDependsOn(this, checkChild, child)) {
  30. if (type == EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) {
  31. // If this is from a pre-draw and we have already been changed
  32. // from a nested scroll, skip the dispatch and reset the flag
  33. checkLp.resetChangedAfterNestedScroll();
  34. continue;
  35. }
  36. final boolean handled;
  37. switch (type) {
  38. case EVENT_VIEW_REMOVED:
  39. // EVENT_VIEW_REMOVED means that we need to dispatch
  40. // onDependentViewRemoved() instead
  41. b.onDependentViewRemoved(this, checkChild, child);
  42. handled = true;
  43. break;
  44. default:
  45. // Otherwise we dispatch onDependentViewChanged()
  46. handled = b.onDependentViewChanged(this, checkChild, child);
  47. break;
  48. }
  49. if (type == EVENT_NESTED_SCROLL) {
  50. // If this is from a nested scroll, set the flag so that we may skip
  51. // any resulting onPreDraw dispatch (if needed)
  52. checkLp.setChangedAfterNestedScroll(handled);
  53. }
  54. }
  55. }
  56. }
  57. releaseTempRect(inset);
  58. releaseTempRect(drawRect);
  59. releaseTempRect(lastDrawRect);
  60. }

layout 过程

  1. @Override
  2. protected void onLayout(boolean changed, int l, int t, int r, int b) {
  3. final int layoutDirection = ViewCompat.getLayoutDirection(this);
  4. final int childCount = mDependencySortedChildren.size();
  5. for (int i = 0; i < childCount; i++) {
  6. final View child = mDependencySortedChildren.get(i);
  7. if (child.getVisibility() == GONE) {
  8. // If the child is GONE, skip...
  9. continue;
  10. }
  11. final LayoutParams lp = (LayoutParams) child.getLayoutParams();
  12. final Behavior behavior = lp.getBehavior();
  13. if (behavior == null || !behavior.onLayoutChild(this, child, layoutDirection)) {
  14. onLayoutChild(child, layoutDirection);
  15. }
  16. }
  17. }

CoordinatorLayout 的事件传递

CoordinatorLayout 并不会直接处理事件,而是会尽可能地交给子 View 的Behavior 进行处理。onInterceptTouchEvent 和 onToucheEvent 这两个方法都会调用 performIntercept 来处理事件。

  1. private boolean performIntercept(MotionEvent ev, final int type) {
  2. boolean intercepted = false;
  3. boolean newBlock = false;
  4. MotionEvent cancelEvent = null;
  5. final int action = MotionEventCompat.getActionMasked(ev);
  6. final List<View> topmostChildList = mTempList1;
  7. //在5.0以上,按照z属性来排序,以下,则是按照添加顺序或者自定义的绘制顺序来排列
  8. getTopSortedChildren(topmostChildList);
  9. // Let topmost child views inspect first
  10. final int childCount = topmostChildList.size();
  11. for (int i = 0; i < childCount; i++) {
  12. final View child = topmostChildList.get(i);
  13. final LayoutParams lp = (LayoutParams) child.getLayoutParams();
  14. final Behavior b = lp.getBehavior();
  15. if ((intercepted || newBlock) && action != MotionEvent.ACTION_DOWN) {
  16. // Cancel all behaviors beneath the one that intercepted.
  17. // If the event is "down" then we don't have anything to cancel yet.
  18. // 如果有一个behavior对事件进行了拦截,就发送Cancel事件给后续的所有Behavior。
  19. //假设之前还没有Intercept发生,那么所有的事件都平等地对所有含有behavior的view进行分发,
  20. //现在intercept忽然出现,那么相应的我们就要对除了Intercept的view发出Cancel
  21. if (b != null) {
  22. if (cancelEvent == null) {
  23. final long now = SystemClock.uptimeMillis();
  24. cancelEvent = MotionEvent.obtain(now, now,
  25. MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
  26. }
  27. switch (type) {
  28. case TYPE_ON_INTERCEPT:
  29. b.onInterceptTouchEvent(this, child, cancelEvent);
  30. break;
  31. case TYPE_ON_TOUCH:
  32. b.onTouchEvent(this, child, cancelEvent);
  33. break;
  34. }
  35. }
  36. continue;
  37. }
  38. if (!intercepted && b != null) {
  39. switch (type) {
  40. case TYPE_ON_INTERCEPT:
  41. intercepted = b.onInterceptTouchEvent(this, child, ev);
  42. break;
  43. case TYPE_ON_TOUCH:
  44. intercepted = b.onTouchEvent(this, child, ev);
  45. break;
  46. }
  47. // 记录拦截事件 的child
  48. if (intercepted) {
  49. mBehaviorTouchView = child;
  50. }
  51. }
  52. // Don't keep going if we're not allowing interaction below this.
  53. // Setting newBlock will make sure we cancel the rest of the behaviors.
  54. final boolean wasBlocking = lp.didBlockInteraction();
  55. final boolean isBlocking = lp.isBlockingInteractionBelow(this, child);
  56. newBlock = isBlocking && !wasBlocking;
  57. if (isBlocking && !newBlock) {
  58. // Stop here since we don't have anything more to cancel - we already did
  59. // when the behavior first started blocking things below this point.
  60. break;
  61. }
  62. }
  63. topmostChildList.clear();
  64. return intercepted;
  65. }

处理流程大概是这样的


总结

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注