官网介绍:
CoordinatorLayout 有两个主要用途:
1.作为顶级应用程序的装饰或布局
2.作为与一个或多个子视图进行特定交互的容器
首先看构造方法,之后会解析xml中的属性。
<declare-styleable name="CoordinatorLayout">
<attr format="reference" name="keylines"/>
<attr format="reference" name="statusBarBackground"/>
</declare-styleable>
CoordinatorLayoute 分析
CoordinatorLayoute它自己实现了NestedScrollingParent,NestedScrollingParent则是一个接口:
// NestedScrollingParent
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);
public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes);
public void onStopNestedScroll(View target);
public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed);
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);
public boolean onNestedPreFling(View target, float velocityX, float velocityY);
public int getNestedScrollAxes();
由于CoordinatorLayoute 本身不可以滑动,那这些Scroll方法自然不是自己调用的,
是由内部的类调用的。这里要NestedScrollingChild要出场了。
// NestedScrollingChild
public void setNestedScrollingEnabled(boolean enabled);
public boolean isNestedScrollingEnabled();
public boolean startNestedScroll(int axes);
public void stopNestedScroll();
public boolean hasNestedScrollingParent();
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow);
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow);
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);
public boolean dispatchNestedPreFling(float velocityX, float velocityY);//注意,NestedScrollingChild滑动相关都有一个dispatch前缀,这也验证我们之前的说法,NestedScrollingParent相关的方法是由NestedScrollingChild调用的。就像“儿子”做了什么事情,都要像“父亲”汇报一样。“父亲”知道了以后再根据知道的内容做自己的事情 。
NestedScrollingChild,NestedScrollingParent,听名字就知道貌似是一种“父子”关系。前面说到CoordinatorLayout 不可以滑动,那NestedScrollingChild的接口必然是由实现了NestedScrollingChild的可以滚动的类去调用的。那么目前兼容包有
这三个类实现了,我们就挑一个NestedScrollView 分析一下吧:
public class NestedScrollView extends FrameLayout implements NestedScrollingParent,
NestedScrollingChild, ScrollingView
可以看到,它既实现了NestedScrollingChild,也实现了NestedScrollingParent,我们暂只关心它实现了NestedScrollingChild就可以了。果然,我们在
NestedScrollView 的onInterceptTouchEvent
和onTouchEvent 的MotionEvent.ACTION_DOWN中调用了startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
public boolean startNestedScroll(int axes) {
if (hasNestedScrollingParent()) {
// Already in progress
return true;
}
if (isNestedScrollingEnabled()) {
ViewParent p = mView.getParent();// mView即NestedScrollView ,那它的Parent自然为CoordinatorLayoute 了
View child = mView;
while (p != null) {
if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) {
mNestedScrollingParent = p;
ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes);
return true;
}
if (p instanceof View) {
child = (View) p;
}
p = p.getParent();
}
}
return false;
}
在onTouchEvent的MotionEvent.ACTION_MOVE中调用了了dispatchNestedScroll()方法。继而调用了
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
if (dxConsumed != 0 || dyConsumed != 0 || dxUnconsumed != 0 || dyUnconsumed != 0) {
int startX = 0;
int startY = 0;
if (offsetInWindow != null) {
mView.getLocationInWindow(offsetInWindow);
startX = offsetInWindow[0];
startY = offsetInWindow[1];
}
ViewParentCompat.onNestedScroll(mNestedScrollingParent, mView, dxConsumed,
dyConsumed, dxUnconsumed, dyUnconsumed);
if (offsetInWindow != null) {
mView.getLocationInWindow(offsetInWindow);
offsetInWindow[0] -= startX;
offsetInWindow[1] -= startY;
}
return true;
} else if (offsetInWindow != null) {
// No motion, no dispatch. Keep offsetInWindow up to date.
offsetInWindow[0] = 0;
offsetInWindow[1] = 0;
}
}
return false;
}
之后
ViewParentCompat.onNestedScroll(mNestedScrollingParent, mView, dxConsumed,
dyConsumed, dxUnconsumed, dyUnconsumed);
// ViewParentCompat.onNestedScroll()
public static void onNestedScroll(ViewParent parent, View target, int dxConsumed,
int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
IMPL.onNestedScroll(parent, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
}
// 选一个 IMPL = new ViewParentCompatStubImpl();
@Override
public boolean onStartNestedScroll(ViewParent parent, View child, View target,
int nestedScrollAxes) {
if (parent instanceof NestedScrollingParent) { //指CoordinatorLayoute
return ((NestedScrollingParent) parent).onStartNestedScroll(child, target,
nestedScrollAxes);
}
return false;
}
回到CoordinatorLayoute 中的
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
boolean handled = false;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View view = getChildAt(i);
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) {
final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child, target,
nestedScrollAxes);
handled |= accepted;
lp.acceptNestedScroll(accepted);
} else {
lp.acceptNestedScroll(false);
}
}
return handled;
}
最终调用了循环调用了子Behaviour中的StartNestedScroll。
小结:
1.当CoordinatorLayout作为一个父容器,包含了实现了NestedScrollingChild的可以滚动的RecyclerView /NestedScrollView /SwipeRefreshLayout的时候,它的滑动事件可以回调到CoordinatorLayout类中,在CoordinatorLayout中,它又可以分发事件到各个子View所添加的Behaviour中。在这一过程中,CoordinatorLayout相当一个沟通的桥梁。