本文通过分析CoordinatorLayout的源码,总结了Behavior在CoordinatorLayout中是如何运作的。
1、Behavior
Behavior是CoordinatorLayout的静态抽象类public static abstract class Behavior<V extends View>
,主要包含了以下方法:
第一组:拦截和触摸相关的方法
- public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev)
- public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev)
第二组:依赖相关的方法
- public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency)
- public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency)
第三组:测量和布局相关的方法
- public boolean onMeasureChild(CoordinatorLayout parent, V child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed)
- public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection)
第四组:嵌套滑动相关的方法
- public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes)
- public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes)
- public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target)
- public void onNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed)
- public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed)
- public boolean onNestedFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY, boolean consumed)
- public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY)
我们对这几个方法应该比较熟悉,根据方法的作用,我将它们分成了四组,具体用法下面会分析。
2、CoordinatorLayout
下面对CoordinatorLayout的几个重要方法进行分析:
1、onMeasure方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//该方法执行完成后mDependencySortedChildren中保存了所有排好序的直接子view,被依赖的子view靠前。
//mChildDag保存了CoordinatorLayout直接子view间的依赖关系,
//其中mChildDag.getIncomingEdges(view)返回依赖于view的其他子view,
//mChildDag.getOutgoingEdges(child)返回child依赖的子view。
prepareChildren();
//CoordiantorLayout的直接子view中,如果存在一个子view依赖于其他子view,则添加一个mOnPreDrawListener,
//用于当被依赖的子view改变时,依赖它的子view能做出相应的动作。
ensurePreDrawListener();
...
final int childCount = mDependencySortedChildren.size();
for (int i = 0; i < childCount; i++) {
final View child = mDependencySortedChildren.get(i);
...
int childWidthMeasureSpec = widthMeasureSpec;
int childHeightMeasureSpec = heightMeasureSpec;
...
//如果子view设置了Behavior并且Behavior的onMeasureChild方法返回true的话CoordiantorLayout的onMeasureChild方法就不会调用,
//相应的子view的onMeasure方法就不会被调用。
//我们可以得出结论:如果给子view设置的Behavior的onMeasureChild方法返回true的话,
//那么Behavior的onMeasureChild方法可以接替子view的onMeasure方法执行子view的测量工作。
if (b == null || !b.onMeasureChild(this, child, childWidthMeasureSpec, keylineWidthUsed,
childHeightMeasureSpec, 0)) {
onMeasureChild(child, childWidthMeasureSpec, keylineWidthUsed,
childHeightMeasureSpec, 0);
}
widthUsed = Math.max(widthUsed, widthPadding + child.getMeasuredWidth() +
lp.leftMargin + lp.rightMargin);
heightUsed = Math.max(heightUsed, heightPadding + child.getMeasuredHeight() +
lp.topMargin + lp.bottomMargin);
childState = View.combineMeasuredStates(childState, child.getMeasuredState());
}
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);
setMeasuredDimension(width, height);
}
2、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();
//跟上面的onMeasure一样的套路,不再多说。
if (behavior == null || !behavior.onLayoutChild(this, child, layoutDirection)) {
onLayoutChild(child, layoutDirection);
}
}
}
3、onInterceptTouchEvent
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
MotionEvent cancelEvent = null;
final int action = ev.getActionMasked();
// Make sure we reset in case we had missed a previous important event.
if (action == MotionEvent.ACTION_DOWN) {
resetTouchBehaviors();
}
final boolean intercepted = performIntercept(ev, TYPE_ON_INTERCEPT);
if (cancelEvent != null) {
cancelEvent.recycle();
}
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
resetTouchBehaviors();
}
return intercepted;
}
onInterceptTouchEvent调用了performIntercept方法来决定是否拦截,传入的type是TYPE_ON_INTERCEPT。我们看一下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;
//返回一个子view的列表,z轴上越靠上的越在列表的前面,便于事件分发
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();
...
//如果还没有被拦截并且child的behavior不为空的话,将event交由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;
}
//如果child的behavior的相应方法返回true,使用mBehaviorTouchView记录这个child,
//不再执行其他child的behavior的相应方法。
if (intercepted) {
mBehaviorTouchView = child;
}
}
...
}
topmostChildList.clear();
return intercepted;
}
在没有Behavior之前,执不执行拦截event事件只能由父view决定(子view调用requestDisallowInterceptTouchEvent()除外),子view不能参与父view的拦截逻辑,现在有了Behavior,如果给子view设置Behavior,子view就可以通过Behavior参与到父view的操作逻辑中。
4、onTouchEvent
@Override
public boolean onTouchEvent(MotionEvent ev) {
boolean handled = false;
boolean cancelSuper = false;
MotionEvent cancelEvent = null;
final int action = ev.getActionMasked();
//如果mBehaviorTouchView不为空,即在CoordiantorLayout的onInterceptTouchEvent中
//存在一个子view的behavior的onInterceptTouchEvent返回true,
//则直接调用这个behavior的onTouchEvent。
//否则同样执行performIntercept方法,传入的type是TYPE_ON_TOUCH,如果performIntercept返回true,则mBehaviorTouchView一定不为null,
//此时直接交由mBehaviorTouchView的behavior的onTouchEvent方法处理。
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);
}
}
// 如果mBehaviorTouchView是null,
//则说明CoordiantorLayout的子view都没有设置behavior或者设置的hehavior的onTouchEvent都是返回的false,
//就继续按照没有behavior的流程进行常规的onTouchEvent()操作。
if (mBehaviorTouchView == null) {
handled |= super.onTouchEvent(ev);
} else if (cancelSuper) {
if (cancelEvent == null) {
final long now = SystemClock.uptimeMillis();
cancelEvent = MotionEvent.obtain(now, now,
MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
}
super.onTouchEvent(cancelEvent);
}
if (!handled && action == MotionEvent.ACTION_DOWN) {
}
if (cancelEvent != null) {
cancelEvent.recycle();
}
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
resetTouchBehaviors();
}
return handled;
}
5、onStartNestedScroll
CoordinatorLayout实现了NestedScrollingParent接口,可以处理嵌套滑动,对嵌套滑动机制不熟悉的可以去查阅相关的资料。
@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();
//如果子view设置了behavior的话,调用behavior的相应onStartNestedScroll方法。
//此处CoordinatorLayout将它的onStartNestedScroll方法委托给了子view的behavior进行处理,
//如果存在一个子view的hehavior接收嵌套滑动,则CoordinatorLayout接受嵌套滑动。
if (viewBehavior != null) {
final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child, target,
nestedScrollAxes);
handled |= accepted;
lp.acceptNestedScroll(accepted);
} else {
lp.acceptNestedScroll(false);
}
}
return handled;
}
6、onNestedScrollAccepted
同onStartNestedScroll差不多的套路
7、onStopNestedScroll
同onStartNestedScroll差不多的套路
8、onNestedScroll
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed) {
final int childCount = getChildCount();
boolean accepted = false;
//遍历子view
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();
//如果子view不接受嵌套滑动,跳过
if (!lp.isNestedScrollAccepted()) {
continue;
}
final Behavior viewBehavior = lp.getBehavior();
//如果子view的hehavior不为空,执行hehavior的onNestedScroll方法
if (viewBehavior != null) {
viewBehavior.onNestedScroll(this, view, target, dxConsumed, dyConsumed,
dxUnconsumed, dyUnconsumed);
accepted = true;
}
}
//因为子view可能会在它的hehavior的onNestedScroll方法中对自己的位置、大小等参数进行更改,
//所以这个地方调用了onChildViewsChanged方法来通知对它有依赖关系的其他子view作出相应的更改。
if (accepted) {
onChildViewsChanged(EVENT_NESTED_SCROLL);
}
}
我们来看一下onChildViewsChanged是什么样的逻辑:
//type有三种:EVENT_PRE_DRAW,EVENT_NESTED_SCROLL,EVENT_NESTED_SCROLL
//EVENT_PRE_DRAW:即将绘制视图树时执行onChildViewsChanged(EVENT_PRE_DRAW);
//EVENT_NESTED_SCROLL:onNestedScroll、onNestedPreScroll、onNestedFling中选择性执行onChildViewsChanged(EVENT_NESTED_SCROLL);
//EVENT_VIEW_REMOVED:当有子view被Remove的时候执行onChildViewsChanged(EVENT_VIEW_REMOVED)。
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();
//遍历所有子view
for (int i = 0; i < childCount; i++) {
final View child = mDependencySortedChildren.get(i);
...
//遍历当前子view后面的子view
//(前面在prepareChildren()方法中将子view都放到了mDependencySortedChildren里面,
//并且是排好序的即:被依赖的子view在前面,依赖的子view在后面)
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();
//如果后面的子view依赖当前子view
if (b != null && b.layoutDependsOn(this, checkChild, child)) {
if (type == EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) {
// If this is from a pre-draw and we have already been changed
// from a nested scroll, skip the dispatch and reset the flag
checkLp.resetChangedAfterNestedScroll();
continue;
}
final boolean handled;
//调用后面子view的behavior的相应方法来配合当前子view联动。
switch (type) {
case EVENT_VIEW_REMOVED:
// EVENT_VIEW_REMOVED means that we need to dispatch
// onDependentViewRemoved() instead
b.onDependentViewRemoved(this, checkChild, child);
handled = true;
break;
default:
// Otherwise we dispatch 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);
}
9、onNestedPreScroll
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
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()) {
continue;
}
final Behavior viewBehavior = lp.getBehavior();
if (viewBehavior != null) {
mTempIntPair[0] = mTempIntPair[1] = 0;
viewBehavior.onNestedPreScroll(this, view, target, dx, dy, mTempIntPair);
//上面的逻辑跟onNestedScroll差不多。
//这地方注意一下,如果有多个子view的behavior接受嵌套滑动,xConsumed、yConsumed 的取值是其中某一个子view的behavior消耗的最大值,
//而不是所有子view的behavior消耗的值的总合。
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;
//同onNestedScroll
if (accepted) {
onChildViewsChanged(EVENT_NESTED_SCROLL);
}
}
10、onNestedFling
同onStartNestedScroll差不多的套路
11、onNestedPreFling
同onStartNestedScroll差不多的套路
综上:CoordinatorLayout的源码还是挺简单的,Behavior的存在使得子view可以决定自己的大小、位置,可以影响父view的事件传递,可以处理嵌套滑动,较传统的方式还是很新颖的。
后面有时间的话会分析一下AppBarLayout的Behavior、ScrollingViewBehavior是如何具体实现的。
如果大家有什么疑问或文章中有总结的不对的地方,欢迎大家提出来讨论,谢谢大家。