几年前 CoordinatorLayout 刚出来时,创建Activity会自动生成xml布局,默认的就是 CoordinatorLayout 为根节点,并且还有 AppBarLayout 、CollapsingToolbarLayout 等容器,当时看着头大,开始只是按照Demo知道怎么用,但没有深入原理去解读过,当时始终觉得 CoordinatorLayout 容器比较特殊,现在准备做个总结,简化对它的认识。如果不配置 Behavior 的情况下,可以认为 CoordinatorLayout 是个与 FrameLayout 相似的容器,我们先看看它的测量方法 onMeasure()
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
prepareChildren();
ensurePreDrawListener();
...
final int childCount = mDependencySortedChildren.size();
for (int i = 0; i < childCount; i++) {
final View child = mDependencySortedChildren.get(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
...
final Behavior b = lp.getBehavior();
if (b == null || !b.onMeasureChild(this, child, childWidthMeasureSpec, keylineWidthUsed,
childHeightMeasureSpec, 0)) {
onMeasureChild(child, childWidthMeasureSpec, keylineWidthUsed,
childHeightMeasureSpec, 0);
}
...
}
final int width = ViewCompat.resolveSizeAndState(widthUsed, widthMeasureSpec,
childState & ViewCompat.MEASURED_STATE_MASK);
final int height = ViewCompat.resolveSizeAndState(heightUsed, heightMeasureSpec,
childState << ViewCompat.MEASURED_HEIGHT_STATE_SHIFT);
setMeasuredDimension(width, height);
}
这里面是简化后的代码,注意看前面的两个方法
private void prepareChildren() {
mDependencySortedChildren.clear();
for (int i = 0, count = getChildCount(); i < count; i++) {
final View child = getChildAt(i);
final LayoutParams lp = getResolvedLayoutParams(child);
lp.findAnchorView(this, child);
mDependencySortedChildren.add(child);
}
selectionSort(mDependencySortedChildren, mLayoutDependencyComparator);
}
getResolvedLayoutParams() 方法是如果xml布局中没有设置 Behavior 路径,在此用注解方式生成,设置到 LayoutParams 中;selectionSort() 中完成 CoordinatorLayout 中子view按照依赖关系的排列,被依赖的 view 在前面,并把view保存在 mDependencySortedChildren 中。 ensurePreDrawListener() 方法牵涉到 Behavior 中的方法,一会统一讲解。我们再看看
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);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Behavior behavior = lp.getBehavior();
if (behavior == null || !behavior.onLayoutChild(this, child, layoutDirection)) {
onLayoutChild(child, layoutDirection);
}
}
}
注意看for循环中的代码,获取到view的 Behavior,如果为null,或者 Behavior 的 onLayoutChild() 返回为 false,也就是说不需要依赖view的位置决定自己的布局,此时执行 onLayoutChild(child, layoutDirection) 方法,这个方法中会对自己的位置进行布局。我们可以看得出,Behavior 的优先级似乎比 CoordinatorLayout 的高,此时看看 onMeasure()中的 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();
}
}
}
这个是判断 CoordinatorLayout 和子view之间是否有依赖,如果有,添加监听回调
void addPreDrawListener() {
if (mIsAttachedToWindow) {
// Add the listener
if (mOnPreDrawListener == null) {
mOnPreDrawListener = new OnPreDrawListener();
}
final ViewTreeObserver vto = getViewTreeObserver();
vto.addOnPreDrawListener(mOnPreDrawListener);
}
mNeedsPreDrawListener = true;
}
class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
@Override
public boolean onPreDraw() {
dispatchOnDependentViewChanged(false);
return true;
}
}
会调用 dispatchOnDependentViewChanged() 方法,这个方法中,最关键的还是下面缩减的代码
void dispatchOnDependentViewChanged(final boolean fromNestedScroll) {
final int layoutDirection = ViewCompat.getLayoutDirection(this);
final int childCount = mDependencySortedChildren.size();
for (int i = 0; i < childCount; i++) {
...
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)) {
...
final boolean handled = b.onDependentViewChanged(this, checkChild, child);
...
}
}
}
}
for循环中,根据依赖关系,通过 Behavior 的 layoutDependsOn() 方法判断是否依赖,然后通过 onDependentViewChanged() 方法来控制依赖view的位置变化,最常见的场景是控件 A 和 B,需要 B 随着 A 的位移而位移,这时候,就可以自定义 Behavior,在 layoutDependsOn() 中判断要要依赖的view 是否是A,然后在 onDependentViewChanged() 方法中获取A的位置,计算出B的位置,进行位移。
Behavior 的 onMeasureChild() 和 onLayoutChild() 方法,是在 CoordinatorLayout 的 onMeasure() 和 onLayout() 中调用,可见,CoordinatorLayout 也是先把子view的测量和布局先让子view对应的 Behavior 来判断是否执行,如果不执行,CoordinatorLayout 才会走自己的逻辑。
Behavior 的方法比较多,上面简单的介绍了几个,现在看看 onDependentViewRemoved() 方法,看它的名字是被依赖的view从 CoordinatorLayout 中移除时的回调。ViewGroup 本身有移除view的监听,CoordinatorLayout 在构造方法中会去注册 setOnHierarchyChangeListener(new HierarchyChangeListener()),
private class HierarchyChangeListener implements OnHierarchyChangeListener {
@Override
public void onChildViewAdded(View parent, View child) {
if (mOnHierarchyChangeListener != null) {
mOnHierarchyChangeListener.onChildViewAdded(parent, child);
}
}
@Override
public void onChildViewRemoved(View parent, View child) {
dispatchDependentViewRemoved(child);
if (mOnHierarchyChangeListener != null) {
mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
}
}
}
void dispatchDependentViewRemoved(View view) {
final int childCount = mDependencySortedChildren.size();
boolean viewSeen = false;
for (int i = 0; i < childCount; i++) {
final View child = mDependencySortedChildren.get(i);
if (child == view) {
viewSeen = true;
continue;
}
if (viewSeen) {
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams)
child.getLayoutParams();
CoordinatorLayout.Behavior b = lp.getBehavior();
if (b != null && lp.dependsOn(this, child, view)) {
b.onDependentViewRemoved(this, child, view);
}
}
}
}
在这里可以看到,一旦触发回调,会执行 dispatchDependentViewRemoved() 方法,Behavior 中也是根据 dependsOn() 方法返回值,才会执行 onDependentViewRemoved()方法。
CoordinatorLayout 是个 ViewGroup 容器,则自然有 onInterceptTouchEvent() 和 onTouchEvent() 方法,简单的看看
public boolean onInterceptTouchEvent(MotionEvent ev) {
...
final boolean intercepted = performIntercept(ev, TYPE_ON_INTERCEPT);
...
return intercepted;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
...
if (mBehaviorTouchView != null || (cancelSuper = performIntercept(ev, TYPE_ON_TOUCH))) {
final LayoutParams lp = (LayoutParams) mBehaviorTouchView.getLayoutParams();
final Behavior b = lp.getBehavior();
if (b != null) {
handled = b.onTouchEvent(this, mBehaviorTouchView, ev);
}
}
if (mBehaviorTouchView == null) {
handled |= super.onTouchEvent(ev);
} else if (cancelSuper) {
...
super.onTouchEvent(cancelEvent);
}
...
return handled;
}
这两个方法中都调用了 performIntercept() 方法,区别是传进去的参数不一样,
private boolean performIntercept(MotionEvent ev, final int type) {
boolean intercepted = false;
...
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();
// 步骤一
if ((intercepted || newBlock) && action != MotionEvent.ACTION_DOWN) {
if (b != null) {
...
switch (type) {
case TYPE_ON_INTERCEPT:
b.onInterceptTouchEvent(this, child, cancelEvent);
break;
case TYPE_ON_TOUCH:
b.onTouchEvent(this, child, cancelEvent);
break;
}
}
continue;
}
// 步骤二
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;
}
}
...
}
topmostChildList.clear();
return intercepted;
}
根据意思,根据传进去的type值,Behavior 会调用它自身的 onInterceptTouchEvent() 或 onTouchEvent() 方法, ACTION_DOWN 时会执行 步骤二,其他的情况会执行 步骤一。CoordinatorLayout 中的 onTouchEvent() 方法也表明,事件分发机制还是优先交给 Behavior 执行,然后才是自身的逻辑。
至于 Behavior 中剩下几个滑动事件的方法,以 onStartNestedScroll() 为例 ,发现 CoordinatorLayout 中也有 onStartNestedScroll() 方法,关系如下
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
boolean handled = false;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View view = getChildAt(i);
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
final Behavior viewBehavior = lp.getBehavior();
if (viewBehavior != null) {
final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child, target,
nestedScrollAxes);
handled |= accepted;
lp.acceptNestedScroll(accepted);
} else {
lp.acceptNestedScroll(false);
}
}
return handled;
}
发现,都是 CoordinatorLayout 调用 Behavior 中的方法,这更像是个代理,这里简单的说一下自定义 Behavior 滑动事件的几个方法:
onStartNestedScroll(): 嵌套滑动开始(ACTION_DOWN),确定 Behavior 是否要监听此次事件
onStopNestedScroll(): 嵌套滑动结束(ACTION_UP 或 ACTION_CANCEL)
onNestedPreScroll(): 嵌套滑动进行中,要监听的子 View 将要滑动,滑动事件即将被消费(最终被谁消费,可以通过代码控制)
onNestedScroll(): 嵌套滑动进行中,要监听的子 View 的滑动事件已经被消费
onNestedPreFling(): 要监听的子 View 即将快速滑动
onNestedFling(): 要监听的子 View 在快速滑动中
至于 CoordinatorLayout 中对应的这几个方法是如何触发调用的,下一章再介绍。