CoordinatorLayout的使用
CoordinatorLayout:是加强版的FrameLayout;主要用在以下两个实例:
1)作为根布局;
2)作为多个子View特殊交互的容器;
CoordinatorLayout配合AppBarLayout+可滑动View(NestedScrollView,RecyclerView)使用;
AppBarLayout:是一个竖直的线性布局,实现了meterial designs app bar的很多特性;例如滚动手势;其子View应该通过setScrollFlags(int)或者app:layout_scrollFlags属性提供滚动行为;
需要作为CoordinatorLayout的直接子View才能实现其特性,同时还需要一个可以滑动的View(NestedScrollView、RecyclerView)通过app:layout_behavior属性绑定ScrollingViewBehavior,才能知道何时滚动;其中主要用到了View的事件传递及分发机制相关内容;
AppBarLayout的五种Flag;
<!--layout_scrollFlags 总共有:五种状态
1: scroll:和可滑动部分一起滑动;
2: scroll|enterAlways: 上滑:往上滑动消失;下滑:显示出来;
3: scroll|enterAlways|enterAlwaysCollapsed 配合 minHeight
上滑往上消失,下滑的时候和2有区别,先滑出minHeight部分;
4:scroll|exitUntilCollapsed 配合minHeight
minHeight部分一直存在,滑到顶,隐藏部分才随之滑出
5:scroll|snap:上滑到一定程度才 消失;下滑到一定程度才出现;
-->
配合 app:layout_behavior="@string/appbar_scrolling_view_behavior";
AppBarLayout中有两个重要的内部类,
Behavior:AppBarLayout默认的Behavior,处理偏移量的滑动;
ScrollingViewBehavior:由可以滚动和嵌套滚动的View来使用(NestedScrollView、RecyclerView);
它们两个都有公共的祖先类ViewOffsetBehavior,继承CoordinatorLayout.Behavior(自动给View设置ViewOffsetHelper);
//相当于将AppBarLayout的Behavior设置AppBarLayout;
@CoordinatorLayout.DefaultBehavior(AppBarLayout.Behavior.class)
public class AppBarLayout extends LinearLayout {}
以下面的布局为例;
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="150dp"
app:layout_scrollFlags="scroll"
app:title="fengluoye2012fengluoye" />
</android.support.design.widget.AppBarLayout>
//layout_behavior相当于将AppBarLayout的ScrollingViewBehavior设置给NestedScrollView;
<android.support.v4.widget.NestedScrollView
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="500dp"
android:background="@color/color_ffa2a2" />
<TextView
android:layout_width="match_parent"
android:layout_height="500dp"
android:background="@color/color_f8771a" />
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>
具体的效果,请修改不同的layout_scrollFlags来看具体效果。
CollapsingToolbarLayout
CollapsingToolbarLayout:折叠工具栏布局;也是一个Fragment,是Toolbar的包装类;被定义为AppBarLayout的子类;
layout_collapseMode:三种
COLLAPSE_MODE_OFF:这个是默认属性,布局将正常显示,没有折叠的行为。
COLLAPSE_MODE_PIN:CollapsingToolbarLayout折叠后,此布局将固定在顶部。
COLLAPSE_MODE_PARALLAX:CollapsingToolbarLayout折叠时,此布局也会有视差折叠效果。配合DEFAULT_PARALLAX_MULTIPLIER 使用;
CoordinatorLayout的相关内容
NestedScrollingParent2接口继承NestedScrollingParent:被ViewGroup的子类实现,支持嵌套子View委托的滑动操作;需要配合NestedScrollingParentHelper类,并将View或ViewGroup方法委托给 NestedScrollingParentHelper类相同的方法区调用;
CoordinatorLayout类静态LayoutParams类:描述CoordinatorLayout子View所需布局的参数。
public static class LayoutParams extends ViewGroup.MarginLayoutParams {
/**
* A {@link Behavior} that the child view should obey.
*/
Behavior mBehavior;
boolean mBehaviorResolved = false;
LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
if (mBehaviorResolved) {
mBehavior = parseBehavior(context, attrs, a.getString(
R.styleable.CoordinatorLayout_Layout_layout_behavior));
}
a.recycle();
if (mBehavior != null) {
// If we have a Behavior, dispatch that it has been attached
mBehavior.onAttachedToLayoutParams(this);
}
}
@Nullable
public Behavior getBehavior() {
return mBehavior;
}
//CoordinatorLayout通过behavior管理子View的layout和交互
public void setBehavior(@Nullable Behavior behavior) {
if (mBehavior != behavior) {
if (mBehavior != null) {
// First detach any old behavior
mBehavior.onDetachedFromLayoutParams();
}
mBehavior = behavior;
mBehaviorTag = null;
mBehaviorResolved = true;
if (behavior != null) {
// Now dispatch that the Behavior has been attached
behavior.onAttachedToLayoutParams(this);
}
}
}
//检查关联的子View是否依赖于CoordinatorLayout的另一个子View。
boolean dependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency == mAnchorDirectChild
|| shouldDodge(dependency, ViewCompat.getLayoutDirection(parent))
|| (mBehavior != null && mBehavior.layoutDependsOn(parent, child, dependency));
}
}
CoordinatorLayout类静态抽象Behavior类:用于CoordinatorLayout的子View的交互的behavior;Behavior实现用户对子View进行拖拽、滑动、甩动或任何其他手势的交互行为。协调Coordinator和子ViewAppBarLayout以及可滑动View(NestedScrollView、RecyclerView)的关键;
public static abstract class Behavior<V extends View> {
//在CoordinatorLayout将事件分发给子View之前,调用onInterceptTouchEvent();
public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
return false;
}
//在Behavior的onInterceptTouchEvent拦截事件之后,调用onTouchEvent()
public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
return false;
}
//所提供的子View是否有另一个兄弟View作为布局依赖;
//如果返回true,那么CoordinatorLayout将1)总是先layout dependency View后在layout 另一个View,不管顺序如何;2)当dependency view的布局和坐标改变之后调用onDependentViewChanged()方法;
public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) {
return false;
}
//在dependency view发生改变之后 调用,来更新child view;通过onLayoutChild()重新layout child view;
public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
return false;
}
//CoordinatorLayout要measure子View的时候调用,主要在CoordinatorLayout的onMeasure()中调用
public boolean onMeasureChild(CoordinatorLayout parent, V child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
return false;
}
//CoordinatorLayout要layout子View的时候调用,主要在CoordinatorLayout的onLayout()中调用
public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) {
return false;
}
@Deprecated
public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull V child, @NonNull View directTargetChild, @NonNull View target,
@ScrollAxis int axes) {
return false;
}
//当CoordinatorLayout的子View初始化nested scroll,调用onStartNestedScroll();
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;
}
@Deprecated
public void onNestedScrollAccepted(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull V child, @NonNull View directTargetChild, @NonNull View target,
@ScrollAxis int axes) {
// Do nothing
}
//当CoordinatorLayout接受nested scroll时调用;
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);
}
}
@Deprecated
public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull V child, @NonNull View target) {
// Do nothing
}
//当nested scroll结束时调用;可以再这个方法中清除状态;
public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull V child, @NonNull View target, @NestedScrollType int type) {
if (type == ViewCompat.TYPE_TOUCH) {
onStopNestedScroll(coordinatorLayout, child, target);
}
}
@Deprecated
public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child,
@NonNull View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed) {
// Do nothing
}
//当Nested scroll已经更新,target view已经滑动或者尝试去滑动;Nested scroll被嵌套的滑动View更新;
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);
}
}
@Deprecated
public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull V child, @NonNull View target, int dx, int dy, @NonNull int[] consumed) {
// Do nothing
}
//Nested scroll更新之前,target view滑动之前;调用
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);
}
}
//当Nested scroll view开始惯性滑动或者将要惯性滑动(手指离开屏幕,View依然在滑动)
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;
}
}
DefaultBehavior:为View定义个默认的Behavior,当这个View作为CoordinatorLayout的子View,能够被LayoutParamsd的setBehavior()方法重写;
@Retention(RetentionPolicy.RUNTIME)
public @interface DefaultBehavior {
Class<? extends Behavior> value();
}
整体流程分析:
CoordiantorLayout的执行流程
主要通过调用CoordinatorLayout类作为父容器的方法,调用其子View的Behavior中的方法;如onMeasureChild()、onLayoutChild()、onInterceptTouchEvent()、onTouchEvent();
CoordinatorLayout的onMeasure():
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
prepareChildren();
ensurePreDrawListener();
//子View的个数;
final int childCount = mDependencySortedChildren.size();
for (int i = 0; i < childCount; i++) {
final View child = mDependencySortedChildren.get(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Behavior b = lp.getBehavior();
//以上面的布局为例,可知AppBarLayout和NestedScrollView的Behavior都不为null,
//调用子View的Behavior的onMeasureChild();
if (b == null || !b.onMeasureChild(this, child, childWidthMeasureSpec, keylineWidthUsed,
childHeightMeasureSpec, 0)) {
onMeasureChild(child, childWidthMeasureSpec, keylineWidthUsed,
childHeightMeasureSpec, 0);
}
}
setMeasuredDimension(width, height);
}
先分别看下AppBarLayout类Behavior和ScrollingViewBehavior的onMeasureChild()方法
AppBarLayout类Behavior的onMeasureChild():获取AppBarLayout的准确高度;
AppBarLayout类ScrollingViewBehavior的onMeasureChild():获取可滑动View的精确高度;
CoordinatorLayout的onLayout()和onMeasure()方法类似,调用Behavior的onLayoutChild()根据其返回值判断;AppbarLayout的Behavior和ScrollingViewBehavior的onLayoutChild()返回true;
@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);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Behavior behavior = lp.getBehavior();
if (behavior == null || !behavior.onLayoutChild(this, child, layoutDirection)) {
onLayoutChild(child, layoutDirection);
}
}
}
AppBarLayout类Behavior的onLayoutChild()
@Override
public boolean onLayoutChild(CoordinatorLayout parent, AppBarLayout abl,
int layoutDirection) {
//返回true;
boolean handled = super.onLayoutChild(parent, abl, layoutDirection);
return handled;
}
ViewOffsetBehavior的onLayoutChild()
@Override
public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) {
// First let lay the child out
layoutChild(parent, child, layoutDirection);
if (mViewOffsetHelper == null) {
//View移动的帮助类,通过offsetLeftAndRight()、offsetTopAndBottom();
mViewOffsetHelper = new ViewOffsetHelper(child);
}
mViewOffsetHelper.onViewLayout();
if (mTempTopBottomOffset != 0) {
mViewOffsetHelper.setTopAndBottomOffset(mTempTopBottomOffset);
mTempTopBottomOffset = 0;
}
if (mTempLeftRightOffset != 0) {
mViewOffsetHelper.setLeftAndRightOffset(mTempLeftRightOffset);
mTempLeftRightOffset = 0;
}
return true;
}
CoordinatorLayout的onInterceptTouchEvent():返回true,表示拦截,执行CoordinatorLayout的OnTouch()方法;返回false,不拦截,将事件传递给子View;调用子View的dispatchTouchEvent()方法;
- 从对AppBarLayout和NestedScrollView的Behavior分析可知,当手指的坐标在AppBarLayout范围内,并且AppBarLayout是可滑动的才会拦截Move事件;NestedScrollView的Behavior默认不拦截,往下分发;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
MotionEvent cancelEvent = null;
final int action = ev.getActionMasked();
final boolean intercepted = performIntercept(ev, TYPE_ON_INTERCEPT);
return intercepted;
}
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;
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();
//获取子View的Behavior;
final Behavior b = lp.getBehavior();
//事件被拦截,并且不是down事件;
if ((intercepted || newBlock) && action != MotionEvent.ACTION_DOWN) {
if (b != null) {
switch (type) {
case TYPE_ON_INTERCEPT:
//调用子View的Behavior的onInterceptTouchEvent()
b.onInterceptTouchEvent(this, child, cancelEvent);
break;
case TYPE_ON_TOUCH:
调用子View的Behavior的onTouchEvent()
b.onTouchEvent(this, child, cancelEvent);
break;
}
}
continue;
}
//事件没有被拦截,并且子View的behavior不为null;type=TYPE_ON_INTERCEPT;
if (!intercepted && b != null) {
switch (type) {
case TYPE_ON_INTERCEPT:
//调用子View的Behavior的onInterceptTouchEvent(),判断是否拦截;
intercepted = b.onInterceptTouchEvent(this, child, ev);
break;
case TYPE_ON_TOUCH:
//调用子View的Behavior的onTouchEvent;
intercepted = b.onTouchEvent(this, child, ev);
break;
}
if (intercepted) {
mBehaviorTouchView = child;
}
}
}
topmostChildList.clear();
return intercepted;
}
依次来看AppBarLayout的Behavior的onInterceptTouchEvent(),在其父类HeaderBehavior中;在Move事件中,在竖直方向有效滑动,拦截事件,返回true;
public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
final int action = ev.getAction();
// move 事件并且mIsBeingDragged= true;
if (action == MotionEvent.ACTION_MOVE && mIsBeingDragged) {
return true;
}
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
mIsBeingDragged = false;
final int x = (int) ev.getX();
final int y = (int) ev.getY();
//可以被拖动并且手指的坐标在child View的范围内;canDragView()被子类AppBarLayout的Behavior重写
if (canDragView(child) && parent.isPointInChildBounds(child, x, y)) {
mLastMotionY = y;
mActivePointerId = ev.getPointerId(0);
//VelocityTracker类计算类似flinging的速度;
ensureVelocityTracker();
}
break;
}
case MotionEvent.ACTION_MOVE: {
final int activePointerId = mActivePointerId;
final int y = (int) ev.getY(pointerIndex);
final int yDiff = Math.abs(y - mLastMotionY);
//有效滑动距离,mIsBeingDragged=true;
if (yDiff > mTouchSlop) {
mIsBeingDragged = true;
mLastMotionY = y;
}
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP: {
mIsBeingDragged = false;
break;
}
}
return mIsBeingDragged;
}
再来看AppBarLayout的ScrollingViewBehavior的onInterceptTouchEvent():默认不拦截;
CoordinatorLayout的onTouchEvent():
@Override
public boolean onTouchEvent(MotionEvent ev) {
boolean handled = false;
boolean cancelSuper = false;
MotionEvent cancelEvent = null;
final int action = ev.getActionMasked();
//在OnInterceptTouchEvent()方法中,如果子View的behavior的拦截事件,会为mBehaviorTouchView赋值;
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();
//调用子View(这里指的是AppBarLayout)的Behavior的onTouchEvent()方法;
if (b != null) {
handled = b.onTouchEvent(this, mBehaviorTouchView, ev);
}
}
//子View的behavior不拦截,并且子View也不消费事件,把CoordinatorLayout当做普通View来处理;
// Keep the super implementation correct
if (mBehaviorTouchView == null) {
//super.onTouchEvent(ev)和handled只要有一个为true,handled就是true;
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);
}
return handled;
}
AppBarLayout的Behavior的OnTouchEvent()在其父类HeaderBehavior中;返回true:表示消费该事件;返回false:表示不消费该事件,往上抛,看其父View的onTouchEvent()是否消费事件;
@Override
public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
final int x = (int) ev.getX();
final int y = (int) ev.getY();
//手指的坐标在 child的范围中,并且child是可滑动的
if (parent.isPointInChildBounds(child, x, y) && canDragView(child)) {
mLastMotionY = y;
} else {
return false;
}
break;
}
case MotionEvent.ACTION_MOVE: {
if (mIsBeingDragged) {
mLastMotionY = y;
// We're being dragged so scroll the ABL
//最后调用ViewOffsetHelper类的setTopAndBottomOffset()方法
scroll(parent, child, dy, getMaxDragOffset(child), 0);
}
break;
}
//VelocityTracker类的使用,获取手指离开屏幕时的滑动速度;
case MotionEvent.ACTION_UP:
if (mVelocityTracker != null) {
mVelocityTracker.addMovement(ev);
mVelocityTracker.computeCurrentVelocity(1000);
float yvel = mVelocityTracker.getYVelocity(mActivePointerId);
//fling
fling(parent, child, -getScrollRangeForDragFling(child), 0, yvel);
}
// $FALLTHROUGH
case MotionEvent.ACTION_CANCEL: {
mIsBeingDragged = false;
break;
}
}
return true;
}
fling()方法
final boolean fling(CoordinatorLayout coordinatorLayout, V layout, int minOffset,
int maxOffset, float velocityY) {
if (mScroller == null) {
mScroller = new OverScroller(layout.getContext());
}
mScroller.fling(
0, getTopAndBottomOffset(), // curr
0, Math.round(velocityY), // velocity.
0, 0, // x
minOffset, maxOffset); // y
//滑动动画没有结束
if (mScroller.computeScrollOffset()) {
mFlingRunnable = new FlingRunnable(coordinatorLayout, layout);
//相当于view.postDelayed(mFlingRunnable);
ViewCompat.postOnAnimation(layout, mFlingRunnable);
return true;
} else {
//AppBarLayout的Behavior类重写了该方法;主要检查AppBarLayout的子View是否有设置scroll_flag有snap;
onFlingFinished(coordinatorLayout, layout);
return false;
}
}
private class FlingRunnable implements Runnable {
@Override
public void run() {
if (mLayout != null && mScroller != null) {
//动画没有完成
if (mScroller.computeScrollOffset()) {
setHeaderTopBottomOffset(mParent, mLayout, mScroller.getCurrY());
// Post ourselves so that we run on the next animation
ViewCompat.postOnAnimation(mLayout, this);
} else {
onFlingFinished(mParent, mLayout);
}
}
}
}
可滑动View(NestedScrollView、RecyclerView)的执行流程
上面主要讲了从CoordinatorLayout的方法讲述如何和子View协调的;当事件传递到NestedScrollView后,我们通过滑动NestedScrollView的时候如何协调的;主要通过NestedScrollView的onInterceptTouchEvent()和onTouchEvent()来调用;
主要调用顺序:NestedScrollView–>CoordinatorLayout—>子View的Behavior;
NestScrollView构造函数
//嵌套滑动的帮助类;非常重要,NestedScrollView通过NestedScrollingChildHelper调用Coordinator的重写NestedScrollingParent2接口的方法,进而调用子View的Behavior的方法;
private final NestedScrollingChildHelper mChildHelper;
public NestedScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initScrollView();
mParentHelper = new NestedScrollingParentHelper(this);
mChildHelper = new NestedScrollingChildHelper(this);
// ...because why else would you be using this widget?
setNestedScrollingEnabled(true);
}
NestedScrollView的onInterceptTouchEvent();
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
return true;
}
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_MOVE: {
final int y = (int) ev.getY(pointerIndex);
final int yDiff = Math.abs(y - mLastMotionY);
//竖直方向,有效滑动拦截事件;
if (yDiff > mTouchSlop
&& (getNestedScrollAxes() & ViewCompat.SCROLL_AXIS_VERTICAL) == 0) {
mIsBeingDragged = true;
mLastMotionY = y;
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
break;
}
case MotionEvent.ACTION_DOWN: {
final int y = (int) ev.getY();
mLastMotionY = y;
mActivePointerId = ev.getPointerId(0);
mScroller.computeScrollOffset();
mIsBeingDragged = !mScroller.isFinished();
//1:startNestedScroll();
startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH);
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange())) {
ViewCompat.postInvalidateOnAnimation(this);
}
stopNestedScroll(ViewCompat.TYPE_TOUCH);
break;
}
return mIsBeingDragged;
}
startNestedScroll():
@Override
public boolean startNestedScroll(int axes, int type) {
return mChildHelper.startNestedScroll(axes, type);
}
NestedScrollingChildHelper类的startNestedScroll();
public boolean startNestedScroll(@ScrollAxis int axes, @NestedScrollType int type) {
//mNestedScrollingParentTouch默认为null;
if (hasNestedScrollingParent(type)) {
// Already in progress
return true;
}
//NestedScrollView构造函数已设置为true;
if (isNestedScrollingEnabled()) {
//按照我们的xml分析来看,p:CoordinatorLayout;child:NestedScrollView;
ViewParent p = mView.getParent();
View child = mView;
while (p != null) {
//
if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes, type)) {
setNestedScrollingParentForType(type, p);
ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes, type);
return true;
}
if (p instanceof View) {
child = (View) p;
}
p = p.getParent();
}
}
return false;
}
public static void onNestedScrollAccepted(ViewParent parent, View child, View target,
int nestedScrollAxes, int type) {
//调用CoordinatorLayout的onNestedScrollAccepted();进而调用behavior的onNestedScrollAccepted()方法,没有被重写,是空方法;
if (parent instanceof NestedScrollingParent2) {
// First try the NestedScrollingParent2 API
((NestedScrollingParent2) parent).onNestedScrollAccepted(child, target,
nestedScrollAxes, type);
} else if (type == ViewCompat.TYPE_TOUCH) {
// Else if the type is the default (touch), try the NestedScrollingParent API
IMPL.onNestedScrollAccepted(parent, child, target, nestedScrollAxes);
}
}
ViewParentCompat的onStartNestedScroll()方法
public static boolean onStartNestedScroll(ViewParent parent, View child, View target,
int nestedScrollAxes, int type) {
//CoordinatorLayout实现了NestedScrollingParent2;调用CoordinatorLayout的onStartNestedScroll();
if (parent instanceof NestedScrollingParent2) {
// First try the NestedScrollingParent2 API
return ((NestedScrollingParent2) parent).onStartNestedScroll(child, target,
nestedScrollAxes, type);
} else if (type == ViewCompat.TYPE_TOUCH) {
// Else if the type is the default (touch), try the NestedScrollingParent API
return IMPL.onStartNestedScroll(parent, child, target, nestedScrollAxes);
}
return false;
}
CoordinatorLayout的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;
}
//调用子View的behavior的onStartNestedScroll();
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有一个为true;handled就是true;
handled |= accepted;
lp.setNestedScrollAccepted(type, accepted);
} else {
lp.setNestedScrollAccepted(type, false);
}
}
return handled;
}
AppBarLayout的Behavior的onStartNestedScroll();返回true:表示AppBarLayout在竖直方向上有可滑动的View;
@Override
public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child,
View directTargetChild, View target, int nestedScrollAxes, int type) {
// 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;
}
AppBarLayout的ScrollingViewBehavior的onStartNestedScroll()方法默认返回false;
NestedScrollView的onTouchEvent();
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (actionMasked) {
case MotionEvent.ACTION_DOWN: {
// Remember where the motion event started
mLastMotionY = (int) ev.getY();
mActivePointerId = ev.getPointerId(0);
//1):startNestedScroll()已经分析过了;
startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH);
break;
}
case MotionEvent.ACTION_MOVE:
final int y = (int) ev.getY(activePointerIndex);
int deltaY = mLastMotionY - y;
//2):dispatchNestedPreScroll()准备开始;
if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset,
}
if (mIsBeingDragged) {
// Calling overScrollByCompat will call onOverScrolled, which
// calls onScrollChanged if applicable.
if (overScrollByCompat(0, deltaY, 0, getScrollY(), 0, range, 0,
0, true) && !hasNestedScrollingParent(ViewCompat.TYPE_TOUCH)) {
// Break our velocity if we hit a scroll barrier.
mVelocityTracker.clear();
}
final int scrolledDeltaY = getScrollY() - oldY;
final int unconsumedY = deltaY - scrolledDeltaY;
//3):dispatchNestedScroll();
if (dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset,
ViewCompat.TYPE_TOUCH)) {
} else if (canOverscroll) {
}
}
break;
case MotionEvent.ACTION_UP:
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
//4)大于系统最小速率;
if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
flingWithNestedDispatch(-initialVelocity);
} else if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0,
getScrollRange())) {
ViewCompat.postInvalidateOnAnimation(this);
}
mActivePointerId = INVALID_POINTER;
endDrag();
break;
case MotionEvent.ACTION_CANCEL:
if (mIsBeingDragged && getChildCount() > 0) {
if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0,
getScrollRange())) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
mActivePointerId = INVALID_POINTER;
endDrag();
break;
}
if (mVelocityTracker != null) {
mVelocityTracker.addMovement(vtev);
}
vtev.recycle();
return true;
}
1)已经分析过了,现在来看下2)、3)和4);
2)NestedScrollView的dispatchNestedPreScroll()
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
dispatchNestedPreScroll(dx, dy, consumed, null);
}
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}
NestedScrollingChildHelper类的dispatchNestedPreScroll();
public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,
@Nullable int[] offsetInWindow) {
return dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, TYPE_TOUCH);
}
public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,
@Nullable int[] offsetInWindow, @NestedScrollType int type) {
//为true;
if (isNestedScrollingEnabled()) {
//在startNestedScroll()中已经为其赋值过;不为null;
final ViewParent parent = getNestedScrollingParentForType(type);
if (parent == null) {
return false;
}
//dy !=0;
}
return false;
}
和startNestedScroll()一样;ViewParentCompat类的onNestedPreScroll()–>CoordinatorLayout的onNestedPreScroll()–>子View的Behavior的onNestedPreScroll();
看下AppBarLayout的Behavior的onNestedPreScroll();
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
View target, int dx, int dy, int[] consumed, int type) {
if (dy != 0) {
}
}
3)dispatchNestedScroll();
@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
int dyUnconsumed, int[] offsetInWindow, int type) {
return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow, type);
}
NestedScrollingChildHelper的dispatchNestedScroll()
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow,
@NestedScrollType int type) {
if (isNestedScrollingEnabled()) {
if (dxConsumed != 0 || dyConsumed != 0 || dxUnconsumed != 0 || dyUnconsumed != 0) {
//内部调用CoordinatorLayout的onNestedScroll();然后调用子View的Behavior的onNestedScroll();
ViewParentCompat.onNestedScroll(parent, mView, dxConsumed,
dyConsumed, dxUnconsumed, dyUnconsumed, type);
return true;
} else if (offsetInWindow != null) {
}
}
return false;
}
4)flingWithNestedDispatch();
private void flingWithNestedDispatch(int velocityY) {
final int scrollY = getScrollY();
final boolean canFling = (scrollY > 0 || velocityY > 0)
&& (scrollY < getScrollRange() || velocityY < 0);
//Coordinator的Behavior的dispatchNestedPreFling()返回false;
if (!dispatchNestedPreFling(0, velocityY)) {
dispatchNestedFling(0, velocityY, canFling);
fling(velocityY);
}
}
@Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
return mChildHelper.dispatchNestedPreFling(velocityX, velocityY);
}
NestedScrollingChildHelper类dispatchNestedPreFling();
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
if (isNestedScrollingEnabled()) {
//parent为CoordinatorLayout;
ViewParent parent = getNestedScrollingParentForType(TYPE_TOUCH);
if (parent != null) {
return ViewParentCompat.onNestedPreFling(parent, mView, velocityX,
velocityY);
}
}
return false;
}
public static boolean onNestedPreFling(ViewParent parent, View target, float velocityX,
float velocityY) {
return IMPL.onNestedPreFling(parent, target, velocityX, velocityY);
}
ViewParentCompatBaseImpl的onNestedPreFling()–>CoordinatorLayout的onNestedPreFling()–>子View的Behavior的onNestedPreFling();目前AppBarLayout的Behavior和ScrollingViewBehavior没有重写该方法,默认返回false;
public boolean onNestedPreFling(ViewParent parent, View target, float velocityX,
float velocityY) {
if (parent instanceof NestedScrollingParent) {
return ((NestedScrollingParent) parent).onNestedPreFling(target, velocityX,
velocityY);
}
return false;
}
NestScrollView的dispatchNestedFling():最后调用CoordinatorLayout的onNestedFling()—>子View的Behavior的onNestedFling();目前AppBarLayout的Behavior和ScrollingViewBehavior没有重写该方法,默认返回false;
@Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
}
以上就是CoordinatorLayout及其配合的View的整体流程分析,为我们实现相关效果自定义Behavior提供思路;如有问题请多指教,谢谢!