下图是CoordinatorLayout布局中很常见的一种效果,很多人应该都见过,当我们用手指滑动RecyclerView的时候,不单止RecyclerView会上下滑动,顶部的Toolbar也会随着RecyclerView的滑动隐藏或显现,实现代码的布局如下:
"1.0" encoding="utf-8"?>
.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways|snap"
android:theme=
"@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme=
"@style/ThemeOverlay.AppCompat.Light" />
.support.design.widget.AppBarLayout>
.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior=
"@string/appbar_scrolling_view_behavior" />
.support.design.widget.CoordinatorLayout>
只要父布局是CoordinatorLayout,然后在Toolbar的外层包上一个AppBarLayout,在Toolbar上添加属性layout_scrollFlags=”scroll|enterAlways|snap”,在RecyclerView上添加属性layout_behavior=”@string/appbar_scrolling_view_behavior”,并把AppBarLayout与RecyclerView作为CoordinatorLayout的子控件,就能实现。
实现的方法知道了,但是我们不能单纯满足于此,接下来我们对原理进行分析。
实现以上效果主要是涉及了嵌套滑动机制和Behavior两个知识点。
根据事件分发机制,我们知道触摸事件最终只会由一个控件进行处理,当我们滑动RecyclerView时,事件最终肯定是传给了RecyclerView,并交给它进行处理,Toolbar是不应该能够接收到事件并响应的。我们无法依靠默认的事件分发机制完成gif图上的效果的(当然,我们通过自定义View,修改事件分发是可以实现这个效果)。
因此Google给我们提供了嵌套滑动机制。通过嵌套滑动机制,RecyclerView能够把自身接受到的点击滑动事件传递给父布局CoordinatorLayout,然后CoordinatorLayout把接收到的事件传递给子布局AppBarLayout(Toolbar的父布局),最终滑动事件交给了AppBarLayout进行处理,完成使Toolbar滚出滚进界面等效果。
Google在v4包中提供了四个关键的类以实现嵌套滑动机制:
一般而言,父布局会实现NestedScrollingParent,而滑动列表作为子控件实现NestedScrollingChild,并把事件传给父布局,父布局再根据情况把事件分发到其它子View。而NestedScrollingParentHelper和NestedScrollingChildHelper分别是NestedScrollingParent和NestedScrollingChild的辅助类,具体的逻辑会委托给它们执行。
接下来我们看一下CoordinatorLayout和RecyclerView的源码。
public class CoordinatorLayout extends ViewGroup implements NestedScrollingParent {
......
}
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild {
......
}
通过源码可发现CoordinatorLayout实现了NestedScrollingParent,而RecyclerView实现了NestedScrollingChild。毫无疑问,RecyclerView就是通过嵌套滑动机制把滑动事件传给了CoordinatorLayout,然后CoordinatorLayout把事件传递到AppBarLayout中。
那么实现这些接口需要实现哪些方法呢?我们通过源码来了解下:
public interface NestedScrollingChild {
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);
public boolean dispatchNestedPreFling(float velocityX, float velocityY);
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow);
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow);
public boolean hasNestedScrollingParent();
public boolean isNestedScrollingEnabled();
public void setNestedScrollingEnabled(boolean enabled);
public boolean startNestedScroll(int axes);
public void stopNestedScroll();
}
public interface NestedScrollingParent {
public int getNestedScrollAxes();
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);
public boolean onNestedPreFling(View target, float velocityX, float velocityY);
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);
public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed);
public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes);
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);
public void onStopNestedScroll(View target);
}
看起来要实现的方法很多,也很复杂的样子,但是实质上通过辅助类NestedScrollingChildHelper和NestedScrollingParentHelper能大大减轻工作量,而且有些方法仅仅是作一个判断,并不需要很复杂的逻辑。在后面的源码验证环节中我们也只会着重分析到重点的几个方法。
在这里先说几个比较重要的方法的调用流程与对应关系:
NestedScrollingChild接口的startNestedScroll会在Down事件触发的时候调用,对应NestedScrollingParent的onStartNestedScroll。
NestedScrollingChild接口的dispatchNestedPreScroll会在Move事件触发的时候调用,对应NestedScrollingParent的onNestedPreScroll。
NestedScrollingChild接口的dispatchNestedScroll会在Move事件触发的时候调用,对应NestedScrollingParent的onNestedScroll。
NestedScrollingChild接口的stopNestedScroll会在Up事件触发的时候调用,对应NestedScrollingParent的onStopNestedScroll。
我们可以通过Behaviour观察我们感兴趣的控件的事件,并作出相应的操作。
通过在xml中添加layout_behavior属性可以给控件设置Behaviour,比如在上面的代码中,就是在RecyclerView中添加属性
layout_behavior="@string/appbar_scrolling_view_behavior"
将RecyclerView的Behaviour指定成AppBarLayout的内部类ScrollingViewBehavior。
或者通过注解的方式给控件设置Behaviour,比如AppBarLayout就是通过
@CoordinatorLayout.DefaultBehavior(AppBarLayout.Behavior.class)
定义自身的Behavior为AppBarLayout.Behavior。
需要注意的是,Behavior是CoordinatorLayout的专属属性,设置Behavior的控件需要是CoordinatorLayout的子控件。
在我们上面的事例代码中一共设置有两个Behavior,第一个就是RecyclerView中通过layout_behavior属性进行设置的ScrollingViewBehavior,第二个就是AppBarLayout的代码中通过注解默认设置的一个AppBarLayout.Behavior.class。
当我们要依赖另一个view的状态变化,例如大小、位置、显示状态,我们至少应该重写以下两个方法:
public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) {
return false;
}
public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
return false;
}
第一个方法负责决定依赖哪个View,第二个方法负责根据依赖的View的变化做出响应。
我们的事例中给RecycleView设置的ScrollingViewBehavior也实现了这两个方法,使得RecycleView一直处于AppBarLayout的下方。
当我们要依赖某个实现了NestedScrollingChild的View的滑动状态时,应该重写以下方法:
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,
V child, View directTargetChild, View target, int nestedScrollAxes) {
return false;
}
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target,
int dx, int dy, int[] consumed) {
// Do nothing
}
public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, V child, View target,
float velocityX, float velocityY) {
return false;
}
onStartNestedScroll决定Behavior是否接收嵌套滑动机制传过来的事件;onNestedPreScroll负责接收依赖的View滑动的滑动事件并处理;onNestedPreFling负责接收快速滑动时的惯性滑动事件并处理。
我们的事例中AppBarLayout通过注解设置的AppBarLayout.Behavior实现了这3个方法,使得AppBarLayout能够接收到RecycleView传来的滑动事件并响应。
在我们滑动RecyclerView的时候,RecyclerView会通过滑动嵌套机制把接收到的事件传给CoordinatorLayout,然后CoordinatorLayout把事件传给AppBarLayout,AppBarLayout再根据自身的Behavior(AppBarLayout.Behavior.class)做相应的处理,判断是否处理该滑动事件,如果不处理,则事件仍交还给RecyclerView,如果处理,就做出相应的操作,例如将Toolbar滚出或者滚进屏幕,并消耗掉需要的滑动事件。
这时候可能会有人有疑问:当AppBarLayout处理并消耗了RecyclerView传递的滑动事件的时候(既Toolbar上下滑动时),RecyclerView为什么也还能跟随着手指上下移动呢?其实这里RecyclerView并不是跟随着手指移动,而是一直保持在AppBarLayout的正下方。这是因为我们在RecyclerView中添加属性
layout_behavior="@string/appbar_scrolling_view_behavior"
给RecyclerView指定了AppBarLayout$ScrollingViewBehavior,这个Behavior会观察AppBarLayout,当AppBarLayout发生变化时做出相应的操作。正是因为这样,就算RecyclerView把滑动事件交给AppBarLayout处理并消耗掉,它也还能一直保持在AppBarLayout的正下方。
总结:当我们滑动RecyclerView时,Toolbar能上下滚动是由嵌套滑动机制和AppBarLayout.Behavior共同工作完成的。而在Toolbar上下滚动时,RecyclerView也能始终保持在其正下方的功能是由ScrollingViewBehavior实现的。
大致的结论说完了,大家现在脑海里应该有个比较清晰的概念了,我们接下来就通过代码进行验证:
我们先来分析一下RecyclerView是如何把滑动事件传给CoordinatorLayout,即NestedScrollingChild把事件传给NestedScrollingParent,以及接收到事件的CoordinatorLayout又如何把事件分发到AppBarLayout的Behavior上。
事件分发是从Down开始的,因此我们先从RecyclerView的Down事件开始分析。
@Override
public boolean onTouchEvent(MotionEvent e) {
......
switch (action) {
case MotionEvent.ACTION_DOWN: {
mScrollPointerId = e.getPointerId(0);
mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
if (canScrollHorizontally) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
}
if (canScrollVertically) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
}
startNestedScroll(nestedScrollAxis);
} break;
......
}
可以看到在RecyclerView的Down事件的最后一行,我们调用了NestedScrollingChild接口的startNestedScroll(nestedScrollAxis)方法,并把支持的滚动方向作为参数传了进去,这个方法也是嵌套滑动机制中被调用的第一个方法,在这个方法内会决定是否启用嵌套滑动,以及谁来接收处理嵌套滑动传过来的事件。
然后我们来看看startNestedScroll(nestedScrollAxis)方法的内部实现。
@Override
public boolean startNestedScroll(int axes) {
return getScrollingChildHelper().startNestedScroll(axes);
}
......
private NestedScrollingChildHelper getScrollingChildHelper() {
if (mScrollingChildHelper == null) {
mScrollingChildHelper = new NestedScrollingChildHelper(this);
}
return mScrollingChildHelper;
}
startNestedScroll(int axes)方法实质上是通过代理的方式,把逻辑委托给了NestedScrollingChildHelper。那么我们来看下NestedScrollingChildHelper的startNestedScroll(int axes)做了什么:
public boolean startNestedScroll(int axes) {
if (hasNestedScrollingParent()) {
// Already in progress
return true;
}
if (isNestedScrollingEnabled()) {
ViewParent p = mView.getParent();
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;
}
首先调用了NestedScrollingChild接口的实现方法hasNestedScrollingParent(),其内部逻辑是判断mNestedScrollingParent是否等于null,如果不是,则代表嵌套滑动已经开始,就直接return true,不继续往下走。
一般开始的时候mNestedScrollingParent在这里都是还没赋值,是为null的,所以可以继续往下走,接下来通过NestedScrollingChild接口的isNestedScrollingEnabled()方法判断是不是支持NestedScrolling,这里默认是为ture,所以我们继续往下走。
接下来调用了mView.getParent(),通过查看RecyclerView的getScrollingChildHelper()方法,以及NestedScrollingChildHelper的构造函数可知,其实就是调用了RecyclerView的getParent()方法,而RecyclerView的父布局是CoordinatorLayout,所以得到的ViewParent p就是CoordinatorLayout。
然后在while循环中通过ViewParentCompat.onStartNestedScroll(p, child, mView, axes)方法不断寻找需要接收处理RecyclerView分发过来的事件的父布局,如果找到了,就返回true,这时候就会执行if语句中的代码,把接收事件的父布局赋值mNestedScrollingParent。并且调用ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes),并且最后整个方法再返回true,startNestedScroll方法就算是跑完了。
在我们的事例代码中,while循环就只执行一次,把CoordinatorLayout、RecyclerView和axes作为值传了进去。在这里child和mView都是同一个RecyclerView。
既然while循环只执行一次,那就代表ViewParentCompat.onStartNestedScroll(p, child, mView, axes)方法在第一次执行的时候就已经返回true了,也就是代表RecyclerView的直接父布局CoordinatorLayout会接收处理RecyclerView分发过来的事件。那么我们就来看下ViewParentCompat.onStartNestedScroll到底写了什么逻辑。为了方便,我们分析5.0以上的源码(与5.0以下的源码的主要区别在于5.0以下的源码多做了一些版本兼容工作)。
ViewParentCompat.onStartNestedScroll最终调用到ViewParentCompatLollipop的onStartNestedScroll方法:
class ViewParentCompatLollipop {
private static final String TAG = "ViewParentCompat";
public static boolean onStartNestedScroll(ViewParent parent, View child, View target,
int nestedScrollAxes) {
try {
return parent.onStartNestedScroll(child, target, nestedScrollAxes);
} catch (AbstractMethodError e) {
Log.e(TAG, "ViewParent " + parent + " does not implement interface " +
"method onStartNestedScroll", e);
return false;
}
}
......
}
在ViewParentCompatLollipop的onStartNestedScroll方法中,其实主要就一句话:
return parent.onStartNestedScroll(child, target, nestedScrollAxes);
这个parent则是从ViewParentCompat.onStartNestedScroll(p, child, mView, axes)方法传过来的p,也就是CoordinatorLayout。
通过这么一系列的调用,最终从RecyclerView的startNestedScroll方法,调用到了CoordinatorLayout的onStartNestedScroll方法。那么接下来我们就去看下CoordinatorLayout的onStartNestedScroll方法中做了什么。
@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);
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不为空,则根据该Behavior的onStartNestedScroll来决定是否把接收来的事件发放给该Behavior所属的View,并返回Behavior的onStartNestedScroll方法的返回值。由于handled |= accepted,只要有一个Behaviorr的onStartNestedScroll方法返回true,handled就会是ture。
也就是:AppBarLayout是否接收事件并处理,是RecyclerView通过嵌套滑动原理,把事件传给CoordinatorLayout,CoordinatorLayout通过遍历自身的子布局,找到了AppBarLayout,并根据AppBarLayout的Behavior是否对事件感兴趣来决定。
在我们这个实例中一共有两个View设置了Behavior,究竟是哪个Behavior处理了事件呢?我们先去看下AppBarLayout的Behavior的源码。AppBarLayout的Behavior我们在上面也已经说过了,是通过注解设置的
@CoordinatorLayout.DefaultBehavior(AppBarLayout.Behavior.class)
我们接下来到AppBarLayout.Behavior里看看它的onStartNestedScroll做了些什么。
@Override
public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child,
View directTargetChild, View target, int nestedScrollAxes) {
// Return true if we're nested scrolling vertically, and we have scrollable children
// and the scrolling view is big enough to scroll
final boolean started = (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0
&& child.hasScrollableChildren()
&& parent.getHeight() - directTargetChild.getHeight() <= child.getHeight();
if (started && mOffsetAnimator != null) {
// Cancel any offset animation
mOffsetAnimator.cancel();
}
// A new nested scroll has started so clear out the previous ref
mLastNestedScrollingChildRef = null;
return started;
}
该方法最终返回一个布尔值started,只有当可垂直滑动、AppBarLayout里有可以滑动的子View、并且CoordinatorLayout的高减去RecyclerView的高小于等于AppBarLayout的高的时候,started等于true,这些条件在上面的事例中都是符合的,因此最终AppBarLayout.Behavior的onStartNestedScroll方法返回true,也就是嵌套滑动的事件交给了AppBarLayout处理。
我们再去看下RecyclerView中设置的ScrollingViewBehavior的源码,ScrollingViewBehavior以及它的父类并没有重写onStartNestedScroll,所以它的onStartNestedScroll方法既是CoordinatorLayout.Behavior:
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,
V child, View directTargetChild, View target, int nestedScrollAxes) {
return false;
}
我们可以看到,ScrollingViewBehavior的onStartNestedScroll方法居然直接返回false了,也就是说它肯定是不会接收通过该方法传来的事件了。
就这样,Down事件就大致分析完了。在Down事件中主要是决定嵌套滑动的接收者,以及对相应的View进行标记,方便Move事件的相关滑动操作。
Down事件分析完了,接下来我们就来分析Move事件,由于代码比较长,我就只截取一部分:
case MotionEvent.ACTION_MOVE: {
......
if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) {
dx -= mScrollConsumed[0];
dy -= mScrollConsumed[1];
vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
// Updated the nested offsets
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
}
......
break;
}
我们主要关注NestedScrollingParent接口的dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)方法,该方法最终调用到CoordinatorLayout的onNestedPreScroll方法,CoordinatorLayout的onNestedPreScroll又会调用到AppBarLayout.Behavir的onNestedPreScroll,调用流程跟Down事件差不多,具体原理限于篇幅就不再分析了,我们主要dispatchNestedPreScroll方法主要实现了什么功能。
该方法主要是决定是否需要把Coordinatorlayout接收到的事件分发给AppBarLayout。假设AppBarLayout中的Toolbar已经完全显示了,而此时RecycleView是在往下滑,这时候Toolbar完全不需要接收事件使自己显示,此时dispatchNestedPreScroll就会返回false。
接下来我们来关注dispatchNestedPreScroll方法的参数,前两个分别是横坐标和纵坐标的偏移量,这没啥好解释的,我们主要来分析后两个参数的作用。后两个参数分别是父view消费掉的 scroll长度(CoordinatorLayout分发给AppBarlayout消费掉)和子View(RecycleView)的窗体偏移量。
如果dispatchNestedPreScroll返回true,则会根据后两个参数来进行修正,例如通过mScrollConsumed更新dx和dy,以及通过mScrollOffset更新RecycleView的窗体偏移量。
假设RecyclerView的滚动事件没有被消费完,在RecycleView的Move事件最后scrollByInternal方法会继续处理剩下的滚动事件,并调用NestedScrollingChild接口的dispatchNestedScroll方法,而且最终还是会通过NestedScrollingParent的onNestedScroll调用到Behavior的对应方法。scrollByInternal方法部分核心代码如下:
......
if (x != 0) {
consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
unconsumedX = x - consumedX;
}
if (y != 0) {
consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
unconsumedY = y - consumedY;
}
......
if (dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset)) {
// Update the last touch co-ords, taking any scroll offset into account
mLastTouchX -= mScrollOffset[0];
mLastTouchY -= mScrollOffset[1];
if (ev != null) {
ev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
}
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
......
}
RecyclerView处理了剩余的滚动距离之后,计算出对剩余滚动事件的消费情况,通过 dispatchNestedScroll 方法分发给CoordinatorLayout,CoordinatorLayout 则通过 onNestedScroll 方法分发给感兴趣的 子View 的 Behavior 处理。然后根据mScrollOffset更新窗体偏移量。具体实现可以自行去查看源码。
最后我们来分析Up事件,先来看代码:
case MotionEvent.ACTION_UP: {
mVelocityTracker.addMovement(vtev);
eventAddedToVelocityTracker = true;
mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
final float xvel = canScrollHorizontally ?
-VelocityTrackerCompat.getXVelocity(mVelocityTracker, mScrollPointerId) : 0;
final float yvel = canScrollVertically ?
-VelocityTrackerCompat.getYVelocity(mVelocityTracker, mScrollPointerId) : 0;
if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {
setScrollState(SCROLL_STATE_IDLE);
}
resetTouch();
} break;
在Up事件最后一行中调用了resetTouch(),而resetTouch又调用了NestedScrollingChild的stopNestedScroll(),然后又是跟上面的流程一样一路调用到NestedScrollingParent的onStopNestedScroll方法,然后再调用对应的Behavior的onStopNestedScroll方法,流程都类似,就不贴代码了。
在Stop的这一流程中主要是将之前在Start流程中的设置清空,比如将mNestedScrollingParent = null(不执行这句的话嵌套滑动就执行不起来了,具体可参考NestedScrollingChild的startNestedScroll方法第一行)。
由嵌套滑动机制和AppBarLayout.Behavior共同工作完成的Toolbar上下滚动效果的原理就分析到这吧。
接下来我们再通过源码分析下RecycleView是如何一直保持在AppBarLayout下方的吧。
在文章开头我已经简单分析过ScrollingViewBehavior主要是依靠layoutDependsOn和onDependentViewChanged方法监听并响应的。ScrollingViewBehavior的layoutDependsOn方法:
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
// We depend on any AppBarLayouts
return dependency instanceof AppBarLayout;
}
很明显,ScrollingViewBehavior就是依赖于AppBarLayout的,那么我们来看下onDependentViewChanged方法:
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child,
View dependency) {
offsetChildAsNeeded(parent, child, dependency);
return false;
}
......
private void offsetChildAsNeeded(CoordinatorLayout parent, View child, View dependency) {
final CoordinatorLayout.Behavior behavior =
((CoordinatorLayout.LayoutParams) dependency.getLayoutParams()).getBehavior();
if (behavior instanceof Behavior) {
// Offset the child, pinning it to the bottom the header-dependency, maintaining
// any vertical gap and overlap
final Behavior ablBehavior = (Behavior) behavior;
ViewCompat.offsetTopAndBottom(child, (dependency.getBottom() - child.getTop())
+ ablBehavior.mOffsetDelta
+ getVerticalLayoutGap()
- getOverlapPixelsForOffset(dependency));
}
}
这两个方法在CoordinatorLayout的onChildViewsChanged会被调用到,而每次重绘时,都会调用onChildViewsChanged,从而使其一直位于AppBarLayout的下方。
好了 到这里就全部分析完毕,欢迎拍砖。