CoordinatorLayout.Behavior详解
CoordinatorLayout is intended for two primary use cases:
As a top-level application decor or chrome layout;
As a container for a specific interaction with one or more child views
我们常用的是第二种情况居多
CoordinatorLayout 结合 AppBarLayout,CollapsingToolbarLayout 和 Toolbar 一起使用,可以给我们的应用带来更多的交互效果。 它们的布局关系
<android.support.design.widget.CoordinatorLayout...>
<android.support.design.widget.AppBarLayout...>
<android.support.design.widget.CollapsingToolbarLayout...>
<!-- your collapsed view -->
<View.../>
<android.support.v7.widget.Toolbar.../>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<!-- Scroll view -->
<android.support.v7.widget.RecyclerView.../>
</android.support.design.widget.CoordinatorLayout>这是效果图
先看看它们的几个属性常用的几个布局元素
CoordinatorLayout
这几个属性都是直接在子布局中使用的
app:layout_behavior
指定子 View 的 behavior, 关于 behavior 后面会有详细的论述
app:layout_anchor
指定锚点的 View
app:layout_anchorGravity
指相对于锚 view 的布局重心
app:layout_keyline
AppBarLayout
AppBarLayout 一般作为 CoordinatorLayout 的直接子类使用; AppBarLayout 的子 View 通过设置自身的 srollFlags 进行希望的滑动行为; 如果把 AppBarLayout 放到普通的 ViewGroup 中而不是 CoordinatorLayout 中,AppBarLayout 的功能将不会起作用
app:layout_scrollFlags/ setScrollFlags(int)
app:layout_scrollFlags 标记位是子布局设置是否可滑动
scroll: 滑动
enterAlways: 获取屏幕外,向下滑,会重新出现
exitUntilCollapsed 滑动一定距离出屏幕,会收叠成 minHeight
snap: 在活动停止之后,会自动滑动靠边的一侧
enterAlwaysCollapsed:要与 minHeight 和 enterAlways 一起使用, 当 View 达到 minHeight 的高度时,CollapsingToolbarLayout 开始展开展开完之后,才会进行滚动
```xml <android.support.design.widget.CoordinatorLayout
...
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar_layout"
android:layout_width="match_parent"
android:layout_height="220dp"
android:fitsSystemWindows="true"
android:minHeight="100dp"
app:expandedTitleMarginStart="38dp"
app:layout_scrollFlags="scroll|enterAlwaysCollapsed|enterAlways"/>
.../>```
app:expanded
设置 AppBarLayout 是否展开
CollapsingToolbarLayout
CollapsingToolbarLayout 是一个实现了折叠功能包裹 Toolbar 的 View, 它做一位 AppBarLayout 子 View 使用
app:layout_collapseMode
pin 固定,钉住
parallax 会呈现视觉差,需要collapseParallaxMultiplier(0.0~1.0之间) 视觉差系数一起配合使用在代码中设置
<ImageView
android:id="@+id/img_bg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:scaleType="centerCrop"
android:src="@drawable/coor_bg"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.7"/>app:contentScrim
折叠之后 toolbar 的颜色
Behavior
CoordinatorLayout 的子 View 进行一些交互,需要设置 Behavior 如果是 CoordinatorLayout 内部滑动的 View 需要设置已经为我们提供好的 behavior
app:layout_behavior="@string/appbar_scrolling_view_behavior"
behavior 的方法分为几类,
布局相关的方法
// 给 Behavior 设置 LayoutParams 时会调用
public void onAttachedToLayoutParams(...) {}
// LayoutParams 移除时会调用
public void onDetachedFromLayoutParams() {}
//CoordinatorLayout 在测量时会回调这个方法
public boolean onMeasureChild(...) {
return false;
}
//CoordinatorLayout 在布局时会回调这个方法
public boolean onLayoutChild(...) {
return false;
}事件处理相关的方法
// 是否拦截 CoordinatorLayout 发过了的点击事件
public boolean onInterceptTouchEvent(...) {
return false;
}
// 接收 CoordinatorLayout 发过了的点击事件
public boolean onTouchEvent(...) {
return false;
}滑动事件相关的方法
// 当 CoordinatorLayout 内有 NestedScrollView 开始滑动的时候回调
public boolean onStartNestedScroll(...) {
return false;
}
// 当上面的 onStartNestedScroll 返回 true,会回到改方法
public void onNestedScrollAccepted(...) {}
// 当 CoordinatorLayout 内有 NestedScrollView 停止滑动的时候回调
public void onStopNestedScroll(...) {}
// 当 CoordinatorLayout 内有 NestedScrollView 滑动过程中的回调
public void onNestedScroll(...) {}
// 在 onNestedScroll 之前回调该方法
public void onNestedPreScroll(...) {}
// 是否滑动的惯性事件处理
public boolean onNestedFling(...) {
return false;
}
// 滑动的惯性事件开始的回调
public boolean onNestedPreFling(...) {
return false;
}依赖 View 相关的方法
这也是我们在自定义 Behavior 时一定会重写的方法
// 当前 View 是否依赖指定 View 进行变化
public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) {
return false;
}
// 依赖的 View(dependency)变化时的回调
public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
return false;
}
// 依赖的 View 被移除时的回调
public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency) {
}下面是 behavior 重要的方法
public static abstract class Behavior<V extends View>{
public Behavior() {
}
public Behavior(Context context, AttributeSet attrs) {}
// 给 Behavior 设置 LayoutParams 时会调用
public void onAttachedToLayoutParams(@NonNull CoordinatorLayout.LayoutParams params) {}
// LayoutParams 移除时会调用
public void onDetachedFromLayoutParams() {}
// 是否拦截 CoordinatorLayout 发过了的点击事件
public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
return false;
}
// 接收 CoordinatorLayout 发过了的点击事件
public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
return false;
}
// 设置 Behavior 所在 View 之外的 View 的蒙层颜色
@ColorInt
public int getScrimColor(CoordinatorLayout parent, V child) {
return Color.BLACK;
}
// 设置蒙层的透明度
@FloatRange(from = 0, to = 1)
public float getScrimOpacity(CoordinatorLayout parent, V child) {
return 0.f;
}
// 是否对 Behavior 绑定 View 下面的 View 的进行交互,
// 默认是是根据 getScrimOpacity 的透明度决定的
public boolean blocksInteractionBelow(CoordinatorLayout parent, V child) {
return getScrimOpacity(parent, child) > 0.f;
}
// 当前 View 是否依赖指定 View 进行变化
public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) {
return false;
}
// 依赖的 View(dependency)变化时的回调
public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
return false;
}
// 依赖的 View 被移除时的回调
public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency) {
}
//CoordinatorLayout 在测量时会回调这个方法
public boolean onMeasureChild(CoordinatorLayout parent, V child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
return false;
}
//CoordinatorLayout 在布局时会回调这个方法
public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) {
return false;
}
// 设置 tag
public static void setTag(View child, Object tag) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
lp.mBehaviorTag = tag;
}
// 获取 tag
public static Object getTag(View child) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
return lp.mBehaviorTag;
}
// 当 CoordinatorLayout 内有 NestedScrollView 开始滑动的时候回调
public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull V child, @NonNull View directTargetChild, @NonNull View target,
@ScrollAxis int axes, @NestedScrollType int type) {
if (type == ViewCompat.TYPE_TOUCH) {
return onStartNestedScroll(coordinatorLayout, child, directTargetChild,
target, axes);
}
return false;
}
// 当上面的 onStartNestedScroll 返回 true,会回到改方法
public void onNestedScrollAccepted(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull V child, @NonNull View directTargetChild, @NonNull View target,
@ScrollAxis int axes, @NestedScrollType int type) {
if (type == ViewCompat.TYPE_TOUCH) {
onNestedScrollAccepted(coordinatorLayout, child, directTargetChild,
target, axes);
}
}
// 当 CoordinatorLayout 内有 NestedScrollView 停止滑动的时候回调
public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull V child, @NonNull View target, @NestedScrollType int type) {
if (type == ViewCompat.TYPE_TOUCH) {
onStopNestedScroll(coordinatorLayout, child, target);
}
}
// 当 CoordinatorLayout 内有 NestedScrollView 滑动过程中的回调
public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child,
@NonNull View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed, @NestedScrollType int type) {
if (type == ViewCompat.TYPE_TOUCH) {
onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed,
dxUnconsumed, dyUnconsumed);
}
}
// 在 onNestedScroll 之前回调该方法
public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull V child, @NonNull View target, int dx, int dy, @NonNull int[] consumed,
@NestedScrollType int type) {
if (type == ViewCompat.TYPE_TOUCH) {
onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
}
}
// 是否滑动的惯性事件处理
public boolean onNestedFling(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull V child, @NonNull View target, float velocityX, float velocityY,
boolean consumed) {
return false;
}
// 滑动的惯性事件开始的回调
public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull V child, @NonNull View target, float velocityX, float velocityY) {
return false;
}
// 如果给CoordinatorLayout设置了fitSystemWindow=true,可以在这里自己处理WindowInsetsCompat
@NonNull
public WindowInsetsCompat onApplyWindowInsets(CoordinatorLayout coordinatorLayout,
V child, WindowInsetsCompat insets) {
return insets;
}
// 在CoordinatorLayout的requestChildRectangleOnScreen()中被调用
public boolean onRequestChildRectangleOnScreen(CoordinatorLayout coordinatorLayout,
V child, Rect rectangle, boolean immediate) {
return false;
}
}CoordinatorLayout 与 Behavior 的关系
了解 CoordinatorLayout 与 Behavior 的关系,需要进入 CoordinatorLayout 的源码里面去看看。
CoordinatorLayout#onMeasure
CoordinatorLayout#onMeasure 方法里面会调用 Behavior.#onMeasureChild
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 准备工作, 用 DFS 深度遍历算法,对依赖的 View 进行排序 prepareChildren(); // 根据情况添加或者移除OnPreDrawListener ensurePreDrawListener(); ... final boolean applyInsets = mLastInsets != null && ViewCompat.getFitsSystemWindows(this); final int childCount = mDependencySortedChildren.size(); for (int i = 0; i < childCount; i++) { // 从排好续的集合中依次获取Child Vie final View child = mDependencySortedChildren.get(i); if (child.getVisibility() == GONE) { // If the child is GONE, skip... continue; } final LayoutParams lp = (LayoutParams) child.getLayoutParams(); .... // 处理 FitsSystemWindows int childWidthMeasureSpec = widthMeasureSpec; int childHeightMeasureSpec = heightMeasureSpec; if (applyInsets && !ViewCompat.getFitsSystemWindows(child)) { // We're set to handle insets but this child isn't, so we will measure the // child as if there are no insets ... } // Behavior final Behavior b = lp.getBehavior(); // Behavior.onMeasureChild 方法调用 if (b == null || !b.onMeasureChild(this, child, childWidthMeasureSpec, keylineWidthUsed, childHeightMeasureSpec, 0)) { onMeasureChild(child, childWidthMeasureSpec, keylineWidthUsed, childHeightMeasureSpec, 0); } ... } ... // 设置 width,height setMeasuredDimension(width, height); }CoordinatorLayout#onLayout
CoordinatorLayout#onLayout 方法里面会调用 Behavior#onLayoutChild
@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(); // 调用 Behavior#onLayoutChild, 如果 behavior 不进行测量,则需要 CoordinatorLayout 自己测量 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) { // 设置了 AnchorView 时候的布局, app:layout_anchor layoutChildWithAnchor(child, lp.mAnchorView, layoutDirection); } else if (lp.keyline >= 0) { // 设置了 keyline 的布局, app:layout_keyline layoutChildWithKeyline(child, lp.keyline, layoutDirection); } else { // 正常测量,像 FrameLayout 那样布局 layoutChild(child, layoutDirection); } }CoordinatorLayout#onLayout
CoordinatorLayout#onLayout 方法会调用 Behavior#onInterceptTouchEvent 方法询问是否要拦截,也会调用 调用 Behavior#onTouchEvent 处理点击事件
@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. // 重置 Behavior if (action == MotionEvent.ACTION_DOWN) { resetTouchBehaviors(true); } // performIntercept 进行判断是否拦截 final boolean intercepted = performIntercept(ev, TYPE_ON_INTERCEPT); if (cancelEvent != null) { cancelEvent.recycle(); } if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { resetTouchBehaviors(true); } return intercepted; } 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-order 进行排序 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(); if ((intercepted || newBlock) && action != MotionEvent.ACTION_DOWN) { // Cancel all behaviors beneath the one that intercepted. // If the event is "down" then we don't have anything to cancel yet. // 如果一个 View 把事件拦截了,则把重叠于它之下的 behavior 事件都取消 if (b != null) { if (cancelEvent == null) { final long now = SystemClock.uptimeMillis(); cancelEvent = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); } 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: // 调用 Behavior#onInterceptTouchEvent 方法询问是否要拦截 intercepted = b.onInterceptTouchEvent(this, child, ev); break; case TYPE_ON_TOUCH: // 调用 Behavior#onTouchEvent 处理点击事件 intercepted = b.onTouchEvent(this, child, ev); break; } if (intercepted) { mBehaviorTouchView = child; } } ... } topmostChildList.clear(); return intercepted; }CoordinatorLayout#onTouchEvent
在 CoordinatorLayout#onTouchEvent 方法里面会调用 Behavior#onTouchEvent 是否进行拦截判断
@Override public boolean onTouchEvent(MotionEvent ev) { boolean handled = false; boolean cancelSuper = false; MotionEvent cancelEvent = null; final int action = ev.getActionMasked(); // 如果是 Behavior 拦截了,则把点击事件教给 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); } } // Keep the super implementation correct // 如果是 Behaivor 不拦截,则交给 CoordinatorLayout 的父类处理,按照事件传递流程处理 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(); } // ACTION_UP 事件重置 Behavior if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { resetTouchBehaviors(false); } return handled; }CoordinatorLayout 的嵌套滑动
CoordinatorLayout 是实现了 NestedScrollingParent2,NestedScrollingParent2 继承了 NestedScrollingParent
NestedScrollingParent2.java 的接口
// This interface should be implemented by ViewGroup // subclasses that wish to support scrolling operations // delegated by a nested child view public interface NestedScrollingParent2 extends NestedScrollingParent { boolean onStartNestedScroll(@NonNull View child, @NonNull View target, @ScrollAxis int axes, @NestedScrollType int type); boolean onStartNestedScroll(@NonNull View child, @NonNull View target, @ScrollAxis int axes, @NestedScrollType int type); void onNestedScrollAccepted(@NonNull View child, @NonNull View target, @ScrollAxis int axes, @NestedScrollType int type); void onStopNestedScroll(@NonNull View target, @NestedScrollType int type); void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @NestedScrollType int type); void onNestedPreScroll(@NonNull View target, int dx, int dy, @Nullable int[] consumed, @NestedScrollType int type) }在 CoordinatorLayout 滑动的时候,实现这些滑动方法,都会直接传入到 Behavior 中对应的方法,例如 CoordinatorLayout#onStartNestedScroll 会分发到 Behavior#onStartNestedScroll
@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) { // 分发到 Behavior#onStartNestedScroll final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child, target, axes, type); handled |= accepted; lp.setNestedScrollAccepted(type, accepted); } else { lp.setNestedScrollAccepted(type, false); } } return handled; }关于嵌套滑动,在后续文章中再细说。
当手指滑动的范围在 AppBarLayout 内滑动,CoordinatorLayout 会通过 NestScrollView 的 Behavior 分发事件,让 NestScrollView 产生滑动; 当手指滑动的范围在 NestScrollView 内滑动,CoordinatorLayout 会通过 AppBarLayout 的 Behavior 分发事件,让 AppBarLayout 产生滑;AppBarLayout 默认的 AppBarLayout#Behavior, 不需要显式指定 Behavior。
AppBarLayout 滑动监听
自定义 AppBarStateChangeListener, 同时定义 AppBarLayout 的状态 EXPANDED 展开,COLLAPSED 折叠 和 IDLE 转态
public abstract class AppBarStateChangeListener implements AppBarLayout.OnOffsetChangedListener {
private State mCurrentState = State.IDLE;
@Override
public final void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
if (verticalOffset == 0) {
if (mCurrentState != State.EXPANDED) {
onStateChanged(appBarLayout, State.EXPANDED, verticalOffset);
}
mCurrentState = State.EXPANDED;
} else if (Math.abs(verticalOffset) >= appBarLayout.getTotalScrollRange()) {
if (mCurrentState != State.COLLAPSED) {
onStateChanged(appBarLayout, State.COLLAPSED, verticalOffset);
}
mCurrentState = State.COLLAPSED;
} else {
if (mCurrentState != State.IDLE) {
onStateChanged(appBarLayout, State.IDLE, verticalOffset);
}
mCurrentState = State.IDLE;
}
}
public abstract void onStateChanged(AppBarLayout appBarLayout, State state, int verticalOffset);
}
enum State {
EXPANDED,
COLLAPSED,
IDLE
}然后在 AppBarLayout 中设置监听
appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
// 进行操作
});这个回调是在 AppBarLayout$Behavior#setHeaderTopBottomOffset 方法中回调的。当 AppBarLayout 在进行滑动的时候,会调用 setHeaderTopBottomOffset 方法,在 setHeaderTopBottomOffset在中
@Override
int setHeaderTopBottomOffset(CoordinatorLayout coordinatorLayout,
AppBarLayout appBarLayout, int newOffset, int minOffset, int maxOffset) {
final int curOffset = getTopBottomOffsetForScrollingSibling();
int consumed = 0;
if (minOffset != 0 && curOffset >= minOffset && curOffset <= maxOffset) {
// If we have some scrolling range, and we're currently within the min and max
// offsets, calculate a new offset
newOffset = MathUtils.clamp(newOffset, minOffset, maxOffset);
if (curOffset != newOffset) {
final int interpolatedOffset = appBarLayout.hasChildWithInterpolator()
? interpolateOffset(appBarLayout, newOffset)
: newOffset;
final boolean offsetChanged = setTopAndBottomOffset(interpolatedOffset);
// Update how much dy we have consumed
consumed = curOffset - newOffset;
// Update the stored sibling offset
mOffsetDelta = newOffset - interpolatedOffset;
if (!offsetChanged && appBarLayout.hasChildWithInterpolator()) {
// If the offset hasn't changed and we're using an interpolated scroll
// then we need to keep any dependent views updated. CoL will do this for
// us when we move, but we need to do it manually when we don't (as an
// interpolated scroll may finish early).
coordinatorLayout.dispatchDependentViewsChanged(appBarLayout);
}
// 分发到监听
appBarLayout.dispatchOffsetUpdates(getTopAndBottomOffset());
// Update the AppBarLayout's drawable state (for any elevation changes)
updateAppBarLayoutDrawableState(coordinatorLayout, appBarLayout, newOffset,
newOffset < curOffset ? -1 : 1, false);
}
} else {
// Reset the offset delta
mOffsetDelta = 0;
}
return consumed;
}
}
// 分发到所有的监听器
void dispatchOffsetUpdates(int offset) {
if (mListeners != null) {
for (int i = 0, z = mListeners.size(); i < z; i++) {
final OnOffsetChangedListener listener = mListeners.get(i);
if (listener != null) {
listener.onOffsetChanged(this, offset);
}
}
}注意事项
如果 CoordinatorLayout 里面使用了 ScrollView 或者 ViewPager, 需要指定 Bebeavior
<android.support.design.widget.CoordinatorLayout...>
<android.support.design.widget.AppBarLayout...>
<android.support.design.widget.CollapsingToolbarLayout...>
<!-- your collapsed view -->
<View.../>
<android.support.v7.widget.Toolbar.../>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<!-- 需要指定 appbar_scrolling_view_behavior -->
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</android.support.design.widget.CoordinatorLayout>