CoordinatorLayout与Behavior源码分析

本文通过分析CoordinatorLayout的源码,总结了Behavior在CoordinatorLayout中是如何运作的。

1、Behavior

Behavior是CoordinatorLayout的静态抽象类public static abstract class Behavior<V extends View>,主要包含了以下方法:

第一组:拦截和触摸相关的方法

 - public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) 

 - public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev)

第二组:依赖相关的方法

 - public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency)

 - public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency)

第三组:测量和布局相关的方法

 - public boolean onMeasureChild(CoordinatorLayout parent, V child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed)

 - public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection)

第四组:嵌套滑动相关的方法

 - public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes)

 - public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes)

 - public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target)

 - public void onNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed)

 - public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed)

 - public boolean onNestedFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY, boolean consumed)

 - public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY)

我们对这几个方法应该比较熟悉,根据方法的作用,我将它们分成了四组,具体用法下面会分析。

2、CoordinatorLayout

下面对CoordinatorLayout的几个重要方法进行分析:

1、onMeasure方法

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //该方法执行完成后mDependencySortedChildren中保存了所有排好序的直接子view,被依赖的子view靠前。
        //mChildDag保存了CoordinatorLayout直接子view间的依赖关系,
        //其中mChildDag.getIncomingEdges(view)返回依赖于view的其他子view,
        //mChildDag.getOutgoingEdges(child)返回child依赖的子view。
        prepareChildren();
        //CoordiantorLayout的直接子view中,如果存在一个子view依赖于其他子view,则添加一个mOnPreDrawListener,
        //用于当被依赖的子view改变时,依赖它的子view能做出相应的动作。
        ensurePreDrawListener();

        ...

        final int childCount = mDependencySortedChildren.size();
        for (int i = 0; i < childCount; i++) {
            final View child = mDependencySortedChildren.get(i);

            ...

            int childWidthMeasureSpec = widthMeasureSpec;
            int childHeightMeasureSpec = heightMeasureSpec;

            ...

            //如果子view设置了Behavior并且Behavior的onMeasureChild方法返回true的话CoordiantorLayout的onMeasureChild方法就不会调用,
            //相应的子view的onMeasure方法就不会被调用。
            //我们可以得出结论:如果给子view设置的Behavior的onMeasureChild方法返回true的话,
            //那么Behavior的onMeasureChild方法可以接替子view的onMeasure方法执行子view的测量工作。
            if (b == null || !b.onMeasureChild(this, child, childWidthMeasureSpec, keylineWidthUsed,
                    childHeightMeasureSpec, 0)) {
                onMeasureChild(child, childWidthMeasureSpec, keylineWidthUsed,
                        childHeightMeasureSpec, 0);
            }

            widthUsed = Math.max(widthUsed, widthPadding + child.getMeasuredWidth() +
                    lp.leftMargin + lp.rightMargin);

            heightUsed = Math.max(heightUsed, heightPadding + child.getMeasuredHeight() +
                    lp.topMargin + lp.bottomMargin);
            childState = View.combineMeasuredStates(childState, child.getMeasuredState());
        }

        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);
        setMeasuredDimension(width, height);
    }

2、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();

            //跟上面的onMeasure一样的套路,不再多说。
            if (behavior == null || !behavior.onLayoutChild(this, child, layoutDirection)) {
                onLayoutChild(child, layoutDirection);
            }
        }
    }

3、onInterceptTouchEvent

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        MotionEvent cancelEvent = null;

        final int action = ev.getActionMasked();

        // Make sure we reset in case we had missed a previous important event.
        if (action == MotionEvent.ACTION_DOWN) {
            resetTouchBehaviors();
        }

        final boolean intercepted = performIntercept(ev, TYPE_ON_INTERCEPT);

        if (cancelEvent != null) {
            cancelEvent.recycle();
        }

        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
            resetTouchBehaviors();
        }

        return intercepted;
    }

onInterceptTouchEvent调用了performIntercept方法来决定是否拦截,传入的type是TYPE_ON_INTERCEPT。我们看一下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;
        //返回一个子view的列表,z轴上越靠上的越在列表的前面,便于事件分发
        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();

            ...
            //如果还没有被拦截并且child的behavior不为空的话,将event交由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;
                }
                //如果child的behavior的相应方法返回true,使用mBehaviorTouchView记录这个child,
                //不再执行其他child的behavior的相应方法。
                if (intercepted) {
                    mBehaviorTouchView = child;
                }
            }

            ...

        }

        topmostChildList.clear();

        return intercepted;
    }

在没有Behavior之前,执不执行拦截event事件只能由父view决定(子view调用requestDisallowInterceptTouchEvent()除外),子view不能参与父view的拦截逻辑,现在有了Behavior,如果给子view设置Behavior,子view就可以通过Behavior参与到父view的操作逻辑中。
4、onTouchEvent

@Override
    public boolean onTouchEvent(MotionEvent ev) {
        boolean handled = false;
        boolean cancelSuper = false;
        MotionEvent cancelEvent = null;

        final int action = ev.getActionMasked();

        //如果mBehaviorTouchView不为空,即在CoordiantorLayout的onInterceptTouchEvent中
        //存在一个子view的behavior的onInterceptTouchEvent返回true,
        //则直接调用这个behavior的onTouchEvent。
        //否则同样执行performIntercept方法,传入的type是TYPE_ON_TOUCH,如果performIntercept返回true,则mBehaviorTouchView一定不为null,
        //此时直接交由mBehaviorTouchView的behavior的onTouchEvent方法处理。
        if (mBehaviorTouchView != null || (cancelSuper = performIntercept(ev, TYPE_ON_TOUCH))) {
            // Safe since performIntercept guarantees that
            // mBehaviorTouchView != null if it returns true
            final LayoutParams lp = (LayoutParams) mBehaviorTouchView.getLayoutParams();
            final Behavior b = lp.getBehavior();
            if (b != null) {
                handled = b.onTouchEvent(this, mBehaviorTouchView, ev);
            }
        }

        // 如果mBehaviorTouchView是null,
        //则说明CoordiantorLayout的子view都没有设置behavior或者设置的hehavior的onTouchEvent都是返回的false,
        //就继续按照没有behavior的流程进行常规的onTouchEvent()操作。
        if (mBehaviorTouchView == null) {
            handled |= super.onTouchEvent(ev);
        } else if (cancelSuper) {
            if (cancelEvent == null) {
                final long now = SystemClock.uptimeMillis();
                cancelEvent = MotionEvent.obtain(now, now,
                        MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
            }
            super.onTouchEvent(cancelEvent);
        }

        if (!handled && action == MotionEvent.ACTION_DOWN) {

        }

        if (cancelEvent != null) {
            cancelEvent.recycle();
        }

        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
            resetTouchBehaviors();
        }

        return handled;
    }

5、onStartNestedScroll
CoordinatorLayout实现了NestedScrollingParent接口,可以处理嵌套滑动,对嵌套滑动机制不熟悉的可以去查阅相关的资料。

@Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
        boolean handled = false;

        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View view = getChildAt(i);
            if (view.getVisibility() == View.GONE) {
                // If it's GONE, don't dispatch
                continue;
            }
            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            final Behavior viewBehavior = lp.getBehavior();
            //如果子view设置了behavior的话,调用behavior的相应onStartNestedScroll方法。
            //此处CoordinatorLayout将它的onStartNestedScroll方法委托给了子view的behavior进行处理,
            //如果存在一个子view的hehavior接收嵌套滑动,则CoordinatorLayout接受嵌套滑动。
            if (viewBehavior != null) {
                final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child, target,
                        nestedScrollAxes);
                handled |= accepted;

                lp.acceptNestedScroll(accepted);
            } else {
                lp.acceptNestedScroll(false);
            }
        }
        return handled;
    }

6、onNestedScrollAccepted
同onStartNestedScroll差不多的套路
7、onStopNestedScroll
同onStartNestedScroll差不多的套路
8、onNestedScroll

@Override
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
            int dxUnconsumed, int dyUnconsumed) {
        final int childCount = getChildCount();
        boolean accepted = false;

        //遍历子view
        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();
            //如果子view不接受嵌套滑动,跳过
            if (!lp.isNestedScrollAccepted()) {
                continue;
            }

            final Behavior viewBehavior = lp.getBehavior();
            //如果子view的hehavior不为空,执行hehavior的onNestedScroll方法
            if (viewBehavior != null) {
                viewBehavior.onNestedScroll(this, view, target, dxConsumed, dyConsumed,
                        dxUnconsumed, dyUnconsumed);
                accepted = true;
            }
        }

        //因为子view可能会在它的hehavior的onNestedScroll方法中对自己的位置、大小等参数进行更改,
        //所以这个地方调用了onChildViewsChanged方法来通知对它有依赖关系的其他子view作出相应的更改。
        if (accepted) {
            onChildViewsChanged(EVENT_NESTED_SCROLL);
        }
    }

我们来看一下onChildViewsChanged是什么样的逻辑:

//type有三种:EVENT_PRE_DRAW,EVENT_NESTED_SCROLL,EVENT_NESTED_SCROLL
//EVENT_PRE_DRAW:即将绘制视图树时执行onChildViewsChanged(EVENT_PRE_DRAW);
//EVENT_NESTED_SCROLL:onNestedScroll、onNestedPreScroll、onNestedFling中选择性执行onChildViewsChanged(EVENT_NESTED_SCROLL);
//EVENT_VIEW_REMOVED:当有子view被Remove的时候执行onChildViewsChanged(EVENT_VIEW_REMOVED)。
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();

        //遍历所有子view
        for (int i = 0; i < childCount; i++) {
            final View child = mDependencySortedChildren.get(i);

            ...

            //遍历当前子view后面的子view
            //(前面在prepareChildren()方法中将子view都放到了mDependencySortedChildren里面,
            //并且是排好序的即:被依赖的子view在前面,依赖的子view在后面)
            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();

                //如果后面的子view依赖当前子view
                if (b != null && b.layoutDependsOn(this, checkChild, child)) {
                    if (type == EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) {
                        // If this is from a pre-draw and we have already been changed
                        // from a nested scroll, skip the dispatch and reset the flag
                        checkLp.resetChangedAfterNestedScroll();
                        continue;
                    }

                    final boolean handled;
                    //调用后面子view的behavior的相应方法来配合当前子view联动。
                    switch (type) {
                        case EVENT_VIEW_REMOVED:
                            // EVENT_VIEW_REMOVED means that we need to dispatch
                            // onDependentViewRemoved() instead
                            b.onDependentViewRemoved(this, checkChild, child);
                            handled = true;
                            break;
                        default:
                            // Otherwise we dispatch 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);
    }

9、onNestedPreScroll

@Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        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()) {
                continue;
            }

            final Behavior viewBehavior = lp.getBehavior();
            if (viewBehavior != null) {
                mTempIntPair[0] = mTempIntPair[1] = 0;
                viewBehavior.onNestedPreScroll(this, view, target, dx, dy, mTempIntPair);
                //上面的逻辑跟onNestedScroll差不多。
                //这地方注意一下,如果有多个子view的behavior接受嵌套滑动,xConsumed、yConsumed 的取值是其中某一个子view的behavior消耗的最大值,
                //而不是所有子view的behavior消耗的值的总合。
                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;

        //同onNestedScroll
        if (accepted) {
            onChildViewsChanged(EVENT_NESTED_SCROLL);
        }
    }

10、onNestedFling
同onStartNestedScroll差不多的套路
11、onNestedPreFling
同onStartNestedScroll差不多的套路

综上:CoordinatorLayout的源码还是挺简单的,Behavior的存在使得子view可以决定自己的大小、位置,可以影响父view的事件传递,可以处理嵌套滑动,较传统的方式还是很新颖的。
后面有时间的话会分析一下AppBarLayout的Behavior、ScrollingViewBehavior是如何具体实现的。

如果大家有什么疑问或文章中有总结的不对的地方,欢迎大家提出来讨论,谢谢大家。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值