CoordinatorLayout的源码分析

本文深入解析CoordinatorLayout的使用,包括其作为根布局的功能、与AppBarLayout和可滑动View(如NestedScrollView、RecyclerView)的交互机制,以及Behavior在事件传递和分发中的关键作用。详细分析了AppBarLayout和NestedScrollView的工作流程,探讨了各种scrollFlags的效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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);
}

先分别看下AppBarLayoutBehaviorScrollingViewBehavior的onMeasureChild()方法

AppBarLayoutBehavior的onMeasureChild():获取AppBarLayout的准确高度;

AppBarLayoutScrollingViewBehavior的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);
        }
    }
}

AppBarLayoutBehavior的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提供思路;如有问题请多指教,谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值