前两篇文章介绍了CoordinatorLayout一些基本使用方式和简单自定义的Behavior。为什么CoordinatorLayout能达到这个效果呢。这就不得不对其源码进行分析了,本篇文章就以Behavior中的常用方法为重点,然后通过分析CoordinatorLayout的源码,梳理一下Behavior的调用逻辑和流程。
一、简介
老规矩,先看下Google对其的定义。
CoordinatorLayout is a super-powered
FrameLayout
.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
By specifying
Behaviors
for child views of a CoordinatorLayout you can provide many different interactions within a single parent and those views can also interact with one another. View classes can specify a default behavior when used as a child of a CoordinatorLayout using theCoordinatorLayout.DefaultBehavior
annotation.Behaviors may be used to implement a variety of interactions and additional layout modifications ranging from sliding drawers and panels to swipe-dismissable elements and buttons that stick to other elements as they move and animate.
Children of a CoordinatorLayout may have an
anchor
. This view id must correspond to an arbitrary descendant of the CoordinatorLayout, but it may not be the anchored child itself or a descendant of the anchored child. This can be used to place floating views relative to other arbitrary content panes.Children can specify
CoordinatorLayout.LayoutParams.insetEdge
to describe how the view insets the CoordinatorLayout. Any child views which are set to dodge the same inset edges byCoordinatorLayout.LayoutParams.dodgeInsetEdges
will be moved appropriately so that the views do not overlap.
这巴拉巴拉说了不少东西,有兴趣的可以自己看看。就不逐句翻译了。总的来说CoordinatorLayout,是一个supper-FrameLayout,主要提供两个作用:
1、作为应用的顶层布局;
2、作为一个管理容器,管理与子View或者子View之间的交互。
我们平时使用的时候,主要是就在于第2点上,作为一个ViewGroup要想能够管理交互。那肯定就得拓展东西咯,所以我们先看下该类的一些相关的特殊的属性。
属性 | 对应xml属性 | 用途 |
---|---|---|
AndchorId | layout_anchor &layout_anchorGravity | 布局时根据自身gravity 与 layout_anchorGravity 放置在被anchor的View中 |
Behavior | layout_behavior | 辅助Coordinator对View进行layout、nestedScroll的处理 |
KeyLine | layout_keyline &keylines | 给Coordinator设置了keylines (整数数组)后,可以为子View设置layout_keyline="i" 使其的水平位置根据对应keylines[i] 进行layout。 |
LastChildRect | 无 | 记录每一次Layout的位置,从而判断是否新的一帧改变了位置 |
Behavior大家都听的比较多了,也是我们本篇文章的重点关注对象。这里先对其他三项进行一个说明:
AndchorId:就是布局的时候参考的View的Id。
KeyLine:也是布局的时长作为水平对齐的参考。
这两个的作用也是起到管理子View的作用。作用有点类似于RelativeLayout中子View相互参考进行布局的。只是这两个实际开发中用的很少,所以在分析的时候,可能相关信息就略过了,感兴趣的可以自己研究研究。
现在回归到我们的重点Behavior,前面也介绍了一些简单用法。这里就简单回顾下里面的方法即可,关于方法说明请参考这篇文章
onInterceptTouchEvent()
:是否拦截触摸事件
onTouchEvent()
:处理触摸事件
layoutDependsOn()
:确定使用Behavior
的View
要依赖的View
的类型
onDependentViewChanged()
:当被依赖的View
状态改变时回调
onDependentViewRemoved()
:当被依赖的View
移除时回调
onMeasureChild()
:测量使用Behavior
的View
尺寸
onLayoutChild()
:确定使用Behavior
的View
位置
onStartNestedScroll()
:嵌套滑动开始(ACTION_DOWN
),确定Behavior
是否要监听此次事件
onStopNestedScroll()
:嵌套滑动结束(ACTION_UP
或ACTION_CANCEL
)
onNestedScroll()
:嵌套滑动进行中,要监听的子View
的滑动事件已经被消费
onNestedPreScroll()
:嵌套滑动进行中,要监听的子View
将要滑动,滑动事件即将被消费(但最终被谁消费,可以通过代码控制)
onNestedFling()
:要监听的子View
在快速滑动中
onNestedPreFling()
:要监听的子View
即将快速滑动
具体我们的CoordinatorLayout
是怎么让Behavior
生效的。那就进入我们后面具体流程的分析吧。
二、具体分析
1、创建对象
既然是分析流程嘛,肯定就从构造函数开始了。
public CoordinatorLayout(Context context) {
this(context, null);
}
public CoordinatorLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CoordinatorLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
ThemeUtils.checkAppCompatTheme(context);
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CoordinatorLayout,
defStyleAttr, R.style.Widget_Design_CoordinatorLayout);
final int keylineArrayRes = a.getResourceId(R.styleable.CoordinatorLayout_keylines, 0);
if (keylineArrayRes != 0) {
final Resources res = context.getResources();
mKeylines = res.getIntArray(keylineArrayRes);
final float density = res.getDisplayMetrics().density;
final int count = mKeylines.length;
for (int i = 0; i < count; i++) {
mKeylines[i] = (int) (mKeylines[i] * density);
}
}
mStatusBarBackground = a.getDrawable(R.styleable.CoordinatorLayout_statusBarBackground);
a.recycle();
setupForInsets();
super.setOnHierarchyChangeListener(new HierarchyChangeListener());
}
其实构造函数里主要就是初始化了mKeylines
变量,然后添加了HierarchyChangeListener
监听,其他也没做啥了。
既然构造函数里没有先关的东西,考虑到View的布局的关键流程是从onMeasure()
到onLayout()
,我们也按照这个流程一步步分析咯。
2、布局流程
先看onMeasure()
方法。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 准备工作
prepareChildren();
// 根据情况添加或者移除OnPreDrawListener
ensurePreDrawListener();
final int paddingLeft = getPaddingLeft();
final int paddingTop = getPaddingTop();
final int paddingRight = getPaddingRight();
final int paddingBottom = getPaddingBottom();
final int layoutDirection = ViewCompat.getLayoutDirection(this);
final boolean isRtl = layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL;
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
final int widthPadding = paddingLeft + paddingRight;
final int heightPadding = paddingTop + paddingBottom;
int widthUsed = getSuggestedMinimumWidth();
int heightUsed = getSuggestedMinimumHeight();
int childState = 0;
final boolean applyInsets = mLastInsets != null && ViewCompat.getFitsSystemWindows(this);
final int childCount = mDependencySortedChildren.size();
for (int i = 0; i < childCount; i++) {
// 从排好续的集合中依次获取Child View
final View child = mDependencySortedChildren.get(i);
if (child.getVisibility() == GONE) {
// If the child is GONE, skip...
continue;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
……
int childWidthMeasureSpec = widthMeasureSpec;
int childHeightMeasureSpec = heightMeasureSpec;
// 处理fitsSystemWindows属性
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
final int horizInsets = mLastInsets.getSystemWindowInsetLeft()
+ mLastInsets.getSystemWindowInsetRight();
final int vertInsets = mLastInsets.getSystemWindowInsetTop()
+ mLastInsets.getSystemWindowInsetBottom();
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
widthSize - horizInsets, widthMode);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
heightSize - vertInsets, heightMode);
}
// Behavior相关处理
final Behavior b = lp.getBehavior();
// 调用了Behavior的onMeasureChild()方法
if (b == null || !b.onMeasureChild(this, child, childWidthMeasureSpec, keylineWidthUsed, childHeightMeasureSpec, 0)) { // ①
// 如果Behavior里面没有处理,就调用自己的onMeasureChild()方法进行测量
onMeasureChild(child, childWidthMeasureSpec, keylineWidthUsed,
childHeightMeasureSpec, 0);
}
……
}
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);
// 最后将width和height设置好
setMeasuredDimension(width, height);
}
里面删除了一些无关的代码,说明已经在注释里面有了。我们可以看到在注释①的地方会调用Behavior.onMeasureChild()
,如果我们在该方法里面返回了true的话,就说明我们需要自己测量,CoordinatorLayout
就不会再进行测量,如果没有处理,它就会自己测量。
这里最开始调用了prepareChildren();
和ensurePreDrawListener();
两个方法进行预处理。处理什么呢,我们进入看下:
private void prepareChildren() {
//先清空
mDependencySortedChildren.clear();
mChildDag.clear();
for (int i = 0, count = getChildCount(); i < count; i++) {
final View view = getChildAt(i);
final LayoutParams lp = getResolvedLayoutParams(view);
lp.findAnchorView(this, view);
// 添加进集合 后边排序用
mChildDag.addNode(view);
// Now iterate again over the other children, adding any dependencies to the graph
for (int j = 0; j < count; j++) {
if (j == i) {
continue;
}
final View other = getChildAt(j);
// 判断是否有依赖
if (lp.dependsOn(this, view, other)) { // ①
if (!mChildDag.contains(other)) {
// Make sure that the other node is added
mChildDag.addNode(other);
}
// Now add the dependency to the graph
mChildDag.addEdge(other, view);
}
}
}
// 将排好续的子View集合添加到mDependencySortedChildren
mDependencySortedChildren.addAll(mChildDag.getSortedList()); // ②
// 倒置排序
Collections.reverse(mDependencySortedChildren); // ③
}
在注释①的地方,调用了LayoutParams.dependsOn()
方法进行依赖判断,内部会调用Behavior.layoutDependsOn()
进行判断。这就是我们如果要让两个View产生依赖关系,需要重写该方法返回true的原因了。
注释②的地方,mChildDag.getSortedList()
会根据DFS算法,根据子View的依赖关系进行排序(依赖越深的排在前面,没有依赖的在后面)。最后在注释③的地方,进行倒置排序,也就是没有依赖的在前面,依赖越多的在后面去了。
这个方法分析完了,接着看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();
}
}
}
这里起始就是根据条件,如果子View有依赖关系,并且没有添加监听,那就添加OnPreDrawListener
。如果没有依赖关系,但是已经添加了,就移除。那添加这个作用是啥呢?
那我们就需要看下在这个接口回调里面干了啥
class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
@Override
public boolean onPreDraw() {
onChildViewsChanged(EVENT_PRE_DRAW);
return true;
}
}
里面就调用了onChildViewsChanged(EVENT_PRE_DRAW);
这个方法。继续跟踪
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) {
// GONE 的控件就不用管了
continue;
}
// 获取Child当前的视图位置
getChildRect(child, true, drawRect);
……
if (type != EVENT_VIEW_REMOVED) {
getLastChildRect(child, lastDrawRect);
// 如果不是移除事件 视图位置、大小等信息没有变化 就忽略
if (lastDrawRect.equals(drawRect)) {
continue;
}
recordLastChildRect(child, drawRect);
}
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)) {
// type 为EVENT_PRE_DRAW并且已经通过滑动处理 就不要处理了
if (type == EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) {
checkLp.resetChangedAfterNestedScroll();
continue;
}
final boolean handled;
switch (type) {
case EVENT_VIEW_REMOVED:
// 如果是移除事件 回调Behavior.onDependentViewRemoved()
b.onDependentViewRemoved(this, checkChild, child);
handled = true;
break;
default:
// 其他事件 回调Behavior.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);
}
看完这个方法,发现,这个方法里面就是处理有依赖的时候,并且被依赖的View的属性发送变化的时候,进入Behavior相应的回调方法。具体分析就请看方法注释吧。
到这里,我们的onMeasure()
方法就算是分析完成了,里面主要做了以下几件事情:
1、判断Children是否有依赖关系
2、根据依赖关系将Children进行放入一个排好序的集合中,方便后续使用。
3、根据需要注册OnPreDrawListener
,方便在需要的时候,通知被依赖的子View所依赖的View属性发生了变化
4、根据Behavior.onMeasureChild()
,判断是否需要自己对Child进行测量。
接下里继续跟着布局流程看下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();
if (behavior == null || !behavior.onLayoutChild(this, child, layoutDirection)) {// ①
onLayoutChild(child, layoutDirection);
}
}
}
这个方法比较简单,就是在注释①的地方,根据Behavior.onLayoutChild()方法的返回值,判断是否需要自己调用onLayoutChild()
来摆放Child。所以如果我们想在Behavior中自己控制Child的摆放的话,只需要在该方法中返回true即可。
再跟着流程,看下,默认情况下CoordinatorLayout
是怎么处理Children的摆放的吧。
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) {
// 这里设置了Anchor的情况
layoutChildWithAnchor(child, lp.mAnchorView, layoutDirection);
} else if (lp.keyline >= 0) {
// 设置了KeyLines的情况
layoutChildWithKeyline(child, lp.keyline, layoutDirection);
} else {
// 默认走这里
layoutChild(child, layoutDirection);
}
}
由于Anchor和KeyLine实际很少用到,这里就不做分析了,感兴趣的朋友可以自己研究研究。继续看layoutChild(child, layoutDirection);
private void layoutChild(View child, int layoutDirection) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Rect parent = acquireTempRect();
parent.set(getPaddingLeft() + lp.leftMargin,
getPaddingTop() + lp.topMargin,
getWidth() - getPaddingRight() - lp.rightMargin,
getHeight() - getPaddingBottom() - lp.bottomMargin);
if (mLastInsets != null && ViewCompat.getFitsSystemWindows(this)
&& !ViewCompat.getFitsSystemWindows(child)) {
// If we're set to handle insets but this child isn't, then it has been measured as
// if there are no insets. We need to lay it out to match.
parent.left += mLastInsets.getSystemWindowInsetLeft();
parent.top += mLastInsets.getSystemWindowInsetTop();
parent.right -= mLastInsets.getSystemWindowInsetRight();
parent.bottom -= mLastInsets.getSystemWindowInsetBottom();
}
final Rect out = acquireTempRect();
GravityCompat.apply(resolveGravity(lp.gravity), child.getMeasuredWidth(),
child.getMeasuredHeight(), parent, out, layoutDirection);
child.layout(out.left, out.top, out.right, out.bottom);
releaseTempRect(parent);
releaseTempRect(out);
}
这里就是通过调用child.layout(out.left, out.top, out.right, out.bottom);
进行布局了。
通过这里也可以看到默认没有其他设置的情况,CoordinatorLayout
的布局类似于FrameLayout
到这里,测量布局的流程就算是分析完了。
另外提一点,就是在构造方法里面设置的HierarchyChangeListener监听,再回调里面如果是Child移除了,也会调用onChildViewsChanged()
进行处理。
从布局流程,我们大概知道了Behavior中的layoutDependsOn()
,onDependentViewChanged()
,onDependentViewRemoved()
,onMeasureChild
,onLayoutChild()
这几个方法的调用逻辑。剩下还有几个事件处理和嵌套滑动相关的方法调用逻辑呢。先说触摸事件处理,既然是触摸事件,那肯定就是从onInterceptTouchEvent()
和onTouchEvent()
方法入手了。
3、触摸事件
先来看onInterceptTouchEvent()
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
MotionEvent cancelEvent = null;
final int action = ev.getActionMasked();
// 重置响应的Behavoir
if (action == MotionEvent.ACTION_DOWN) {
resetTouchBehaviors(true);
}
// 具体处理逻辑就在performIntercept()里面了
final boolean intercepted = performIntercept(ev, TYPE_ON_INTERCEPT);
if (cancelEvent != null) {
cancelEvent.recycle();
}
// 重置响应的Behavoir
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
resetTouchBehaviors(true);
}
return intercepted;
}
这里在处理前先重置Behavoir,以免上次的Touch事件遗留的东西有干扰。然后通过performIntercept()
进行处理,同时在cancel或者up的时候,也要进行Behavior的重置。继续看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;
// 先对ChildView进行排序,决定优先处理权
// API>=21时,使用elevation由低到高排列View;API<21时,按View添加顺序排列
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();
// 如果前面的View已经拦截 取消后面的
if ((intercepted || newBlock) && action != MotionEvent.ACTION_DOWN) {
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;
}
// 针对不同的事件调用对用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;
}
if (intercepted) {
mBehaviorTouchView = child;
}
}
// 如果有Child要求阻塞,则就不要处理后续的事件了
final boolean wasBlocking = lp.didBlockInteraction();
final boolean isBlocking = lp.isBlockingInteractionBelow(this, child);
newBlock = isBlocking && !wasBlocking;
if (isBlocking && !newBlock) {
// Stop here since we don't have anything more to cancel - we already did
// when the behavior first started blocking things below this point.
break;
}
}
topmostChildList.clear();
return intercepted;
}
可以看到这个里面,先对ChildView进行排序(越上面的View的优先处理权越高)。然后遍历ChildView,分发给对应的Behavior进行处理,并根据返回值判断是否需要下一个ChildView来处理。
看完onInterceptTouchEvent()
,再看onTouchEvent()
public boolean onTouchEvent(MotionEvent ev) {
boolean handled = false;
boolean cancelSuper = false;
MotionEvent cancelEvent = null;
final int action = ev.getActionMasked();
// 交给performIntercept处理。
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);
}
}
……
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
resetTouchBehaviors(false);
}
return handled;
}
可以看到,内部还是给performIntercept()
方法处理了,上面已经分析过了。
对于触摸事件,我们可以看到,起始CoordinatorLayout
本身自己没有进行处理,主要就是根据需要分发给Behavior
进行代理处理的。
4、嵌套滚动
然后就是最后剩下的嵌套滑动了。这个是怎么处理的呢。我们再回过头来看下CoordinatorLayout
的继承关系。
public class CoordinatorLayout extends ViewGroup implements NestedScrollingParent2 {
……
}
看到了吧,CoordinatorLayout
实现了NestedScrollingParent2
接口。那它在嵌套滚动的机制中,就担任了Parent的角色了。关于嵌套滚动这一块,如果不是很清楚的请看这篇文章,这里就不进行阐述了。只需要知道,如果NestedScrollingParent
(这里的NestedScrollingParent2
继承自NestedScrollingParent
)类型的ViewGroup里面有NestedScrollingChild
类型的子View(如RecyclerView,NestedScrollView),那子View滑动的时候,就会触发嵌套滑动机制,然后进入Parent相应的方法里面。所以这里,我们就看下CoordinatorLayout
对该接口的实现方法吧。
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
return onStartNestedScroll(child, target, nestedScrollAxes, ViewCompat.TYPE_TOUCH);
}
@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);
// 剔除掉页面没有展示的View
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处理
final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child,
target, axes, type);
handled |= accepted;
lp.setNestedScrollAccepted(type, accepted);
} else {
lp.setNestedScrollAccepted(type, false);
}
}
return handled;
}
@Override
public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
onNestedScrollAccepted(child, target, nestedScrollAxes, ViewCompat.TYPE_TOUCH);
}
@Override
public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes, int type) {
mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes, type);
mNestedScrollingTarget = target;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View view = getChildAt(i);
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
if (!lp.isNestedScrollAccepted(type)) {
continue;
}
final Behavior viewBehavior = lp.getBehavior();
if (viewBehavior != null) {
// 交给Behavior处理
viewBehavior.onNestedScrollAccepted(this, view, child, target,
nestedScrollAxes, type);
}
}
}
@Override
public void onStopNestedScroll(View target) {
onStopNestedScroll(target, ViewCompat.TYPE_TOUCH);
}
@Override
public void onStopNestedScroll(View target, int type) {
mNestedScrollingParentHelper.onStopNestedScroll(target, type);
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View view = getChildAt(i);
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
if (!lp.isNestedScrollAccepted(type)) {
continue;
}
final Behavior viewBehavior = lp.getBehavior();
if (viewBehavior != null) {
// 交给Behavior处理
viewBehavior.onStopNestedScroll(this, view, target, type);
}
lp.resetNestedScroll(type);
lp.resetChangedAfterNestedScroll();
}
mNestedScrollingTarget = null;
}
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed) {
onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
ViewCompat.TYPE_TOUCH);
}
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed, int type) {
final int childCount = getChildCount();
boolean accepted = false;
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) {
// 交给Behavior处理
viewBehavior.onNestedScroll(this, view, target, dxConsumed, dyConsumed,
dxUnconsumed, dyUnconsumed, type);
accepted = true;
}
}
if (accepted) {
onChildViewsChanged(EVENT_NESTED_SCROLL);
}
}
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
onNestedPreScroll(target, dx, dy, consumed, ViewCompat.TYPE_TOUCH);
}
@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;
// 交给Behavior处理
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) {
// 通知ChildView布局改变了
onChildViewsChanged(EVENT_NESTED_SCROLL);
}
}
@Override
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
boolean handled = 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(ViewCompat.TYPE_TOUCH)) {
continue;
}
final Behavior viewBehavior = lp.getBehavior();
if (viewBehavior != null) {
// 交给Behavior处理
handled |= viewBehavior.onNestedFling(this, view, target, velocityX, velocityY,
consumed);
}
}
if (handled) {
// 通知ChildView改变了
onChildViewsChanged(EVENT_NESTED_SCROLL);
}
return handled;
}
@Override
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
boolean handled = 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(ViewCompat.TYPE_TOUCH)) {
continue;
}
final Behavior viewBehavior = lp.getBehavior();
if (viewBehavior != null) {
// 交给Behavior处理
handled |= viewBehavior.onNestedPreFling(this, view, target, velocityX, velocityY);
}
}
return handled;
}
@Override
public int getNestedScrollAxes() {
return mNestedScrollingParentHelper.getNestedScrollAxes();
}
如果就是嵌套滑动机制实现,不清楚的请看前面的文章链接,这里就不赘述了。总体上就是交给给Behavior对应的方法进行处理,再根据实际情况,调用onChildViewsChanged()
通知ChildView位置发生了变化。
三、总结
到此,我们CoordinatorLayout
的源码分析就算结束了,当然里面还有很多没有分析到的内容,主要是平时用的也不多,就没有重点介绍,感兴趣的朋友就自己研究下咯。按照惯例,还是做一个简单总结吧。
对于CoordinatorLayout
的使用,除了Google给我们写好的几个类,主要就是自定义Behavior了,所以本篇文章以Behavior几个比较常用的方法为中心,然后分类进行调用逻辑的分析。
1、布局相关
先是在构造方法中注册HierarchyChangeListener
监听ChildView的移除,从而调用Behavior.onDependentViewRemoved()
CoordinatorLayout
会在onMeasure()
方法中,对ChildView根据依赖关系进行排序的同时调用Behavior.layoutDependsOn()
,然后注册OnPreDrawListener
监听ChildView的属性状态的改变以便Behavior.onDependentViewChanged()
的调用。紧接着调用Behavior.onMeasureChild()
在onLayout()
方法中,调用Behavior.onLayoutChild()
方法
2、触摸事件
分别在onInterceptTouchEvent()
和onTouchEvent()
中,通过调用performIntercept()
方法,去执行Behavior.onInterceptTouchEvent()
和Behavior.onTouchEvent()
3、嵌套滚动
这个就是在CoordinatorLayout
实现NestedScrollingParent2
接口的方法中,去调用Behavior对应的嵌套滑动的方法。