CoordinatorLayout
CoordinatorLayout继承自FrameLayout,并且实现了NestedScrollingParent2
接口用于接收嵌套滑动的事件。并且内部定义了一套Behavior
机制,以供所有的子View可以通过Behavior的方式来达到共同参与嵌套滑动的目的。
CoordinatorLayout的使用
通过使用layout_anchor
以及layout_anchorGravity
来定义子View之间的位置关系,并且通过Behavior
自定义子View之间React的交互依赖关系。
NestedScroll流程
- 当子View在XML中使用
app:layout_behavior
指定了对应的Behavior后,会在XML解析的时候将其通过反射机制生成一个Behavior对象保存在LayoutParams中 PS:自定义Behavior类需要定义一个接收Context以及AttributeSet的构造函数,否则无法对象
static Behavior parseBehavior(Context context, AttributeSet attrs, String name) {
if (TextUtils.isEmpty(name)) {
return null;
}
final String fullName;
if (name.startsWith(".")) {
// Relative to the app package. Prepend the app package name.
fullName = context.getPackageName() name;
} else if (name.indexOf('.') >= 0) {
// Fully qualified package name.
fullName = name;
} else {
// Assume stock behavior in this package (if we have one)
fullName = !TextUtils.isEmpty(WIDGET_PACKAGE_NAME)? (WIDGET_PACKAGE_NAME '.' name) : name;
}
try {
Map<String, Constructor<Behavior>> constructors = sConstructors.get();
if (constructors == null) {
constructors = new HashMap<>();
sConstructors.set(constructors);
}
Constructor<Behavior> c = constructors.get(fullName);
if (c == null) {
final Class<Behavior> clazz = (Class<Behavior>) context.getClassLoader()
.loadClass(fullName);
c = clazz.getConstructor(CONSTRUCTOR_PARAMS);
c.setAccessible(true);
constructors.put(fullName, c);
}
return c.newInstance(context, attrs);
} catch (Exception e) {
throw new RuntimeException("Could not inflate Behavior subclass " fullName, e);
}
}
- 根据之前文章分析,在触发嵌套滑时,会回调父View的
onStartNestedScroll
以及onNestedScrollAccepted
方法,遍历所有的子View- 判断子View是否隐藏,如果隐藏则不回调
- 如果子View存在Behavior的话,则通过子View的
onStartNestedScroll
询问是否支持嵌套滑动,如果支持的话,则会在LayoutParams中设置accepted标志位
@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);
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, axes, type);
handled |= accepted;
lp.setNestedScrollAccepted(type, accepted);
} else {
lp.setNestedScrollAccepted(type, false);
}
}
return handled;
}
- 当子View开始滑动的时候,会回调
onNestedPreScroll
函数,遍历子View,判断子View是否有需要消费距离- 判断子View是否隐藏,若隐藏则不参与嵌套滑动
- 判断子View是否允许嵌套滑动,若不允许则不参与嵌套滑动
- 判断子View是否定义了Behavior,若没有则不允许参与嵌套滑动
- 调用子View的
onNestedPreScroll
获取子View所消费的距离 - 将消费最多的距离告知嵌套滑动的View
- 若允许嵌套滑动的话,则调用
onChildViewsChanged
告知所有的子View当前有触发嵌套滑动的事件发生
@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;
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) {
onChildViewsChanged(EVENT_NESTED_SCROLL);
}
}
- 在
onChildViewsChanged
中,会遍历所有存在依赖关系的子View,即mDependencySortedChildren
:- 从
mDependencySortedChildren
中获取有依赖的子View,而mDependencySortedChildren
是在prepareChildren
中初始化完毕的 - 在最后会遍历参与Dependency的View,通过
layoutDependsOn
判断是否两者有依赖关系,如果有的话,则通过onDependentViewChanged
通知子View进行变化
- 从
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) {
// Do not try to update GONE child views in pre draw updates.
continue;
}
// Check child views before for anchor
for (int j = 0; j < i; j ) {
final View checkChild = mDependencySortedChildren.get(j);
...
// Update any behavior-dependent views for the change
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)) {
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;
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);
}
}
}
}
...
}
- 当CoordinatorLayout对子View进行Measure以及Layout的过程中,Behavior也可以充当interpolator进行布局的处理,一切都与Dependency有关
@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);
}
}
}
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) {
layoutChildWithAnchor(child, lp.mAnchorView, layoutDirection);
} else if (lp.keyline >= 0) {
layoutChildWithKeyline(child, lp.keyline, layoutDirection);
} else {
layoutChild(child, layoutDirection);
}
}
- 其余的
NestedPreScroll
与NestedStartScroll
,NestedStopScroll
与上篇文章所述相同