关于CoordinatorLayout AppBarLayout原理的一些分析

本文深入分析了CoordinatorLayout与AppBarLayout配合使用的NestedScrolling机制,解释了其工作原理,包括ACTION_DOWN和ACTION_MOVE事件的处理,并探讨了如何实现平滑的滑动效果。通过对源码的阅读,阐述了事件如何从RecyclerView传递到CoordinatorLayout,再由AppBarLayout消耗,以实现特定的交互效果。
摘要由CSDN通过智能技术生成

这几天学了一些CoordinatorLayout、AppBarLayout配合使用的一些方法,之前还写了一篇CoordinatorLayout Behavior一些笔记,通过这几天对源码的阅读,现在对CoordinatorLayout、AppBarLayout这部分的内容有了更深一层的理解,接下来我就把我所理解的源码简单的分析一下。

一、 NestedScrolling机制

CoordinatorLayout、AppBarLayout分别实现了NestedScrolling机制中需要的接口和接口中的一些方法,如果大家对NestedScrolling不是很了解,可以先去网上了解一下,这里我简单说明一下这个机制的原理:Nested这个单词的意思是“嵌套”,这个机制其实就是嵌套滑动的一种处理机制,它和之前只能单一View消耗滑动事件的处理机制不同,它会在子View处理滑动事件时,先将滑动事件传递到父View中,询问父View是否需要消耗滑动事件,如果父View需要消耗滑动事件,子View会将此次x,y滑动的距离先传递到父View中,父View会先消耗滑动事件,如果父View没消耗全部的滑动距离,子View会消耗剩余的滑动距离,如果剩余的滑动距离大于子View剩余需要的滑动距离(例如RecyclerView距离自身Content滑动到顶部的距离只有10,但是此次滑动距离dy有50,父View消耗了30,剩余20大于RecyclerView剩余需要滑动的距离),子View会把剩下的滑动距离再次传递给父View,由父View去消耗。
我推荐两篇我觉得还挺不错的文章可以帮助理解这个机制:Android NestedScrolling机制完全解析 带你玩转嵌套滑动android NestedScroll嵌套滑动机制完全解析-原来如此简单

二、可以实现的效果

说了这么多,这个机制到底可以实现什么样的效果呢,其实就是滑动起来非常的顺滑,例如,我在界面中放了一个RecyclerView,RecyclerView上面放了一个AppBarLayout包裹的ImageView,当我滑动这个界面时,不会像原来那种机制需要在RecyclerView滑动到顶部时,需要抬起手指进行下次滑动才能把RecyclerView上面的View滑出屏幕以外,效果图如下:
效果图

三、原理分析

下面开始进行我对源码阅读的分析理解,这里主要分成两个部分,主要是RecyclerView、CoordinatorLayout 、AppBarLayout如何实现了NestedScrolling机制。
先简要概括一下总体的中心思想,根据上文对NestedScrolling的介绍,这里的RecyclerView就是子View,CoordinatorLayout就是父View,AppBarLayout是父View在判断是否消耗事件,在判断方法中主要依据的View。主要的过程都是在RecyclerView的onTouchEvent中,分别在Down和Move事件中完成了整个机制的流程。这里说一句题外话,为什么ListView、GirdView不能实现这种效果?因为这两个View并没有实现NestedScrolling机制中相关的方法,可以看一下RecyclerView源码,我们会发现RecycerView定义如下:

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild
1.RecyclerView中MotionEvent.ACTION_DOWN做了哪些事儿?

这里我先来一张流程图:
ActionDown.png
这里所做的一件事儿,就是子View在滑动事件开始时,传递给父View,父View会去判断是否需要消耗此次事件,下面就是源码的分析

//RecyclerView
@Override
    public boolean onTouchEvent(MotionEvent e){
        //.................
        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                mScrollPointerId = e.getPointerId(0);
                mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
                mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);

                int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
                if (canScrollHorizontally) {
                    nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
                }
                if (canScrollVertically) {
                    nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
                }
                //调用NestedScrollingChildHelper
                startNestedScroll(nestedScrollAxis);
            } break;
            //..........
        }

        if (!eventAddedToVelocityTracker) {
            mVelocityTracker.addMovement(vtev);
        }
        vtev.recycle();

        return true;
    }

上面的startNestedScroll方法就会调用到NestedScrollingChildHelper中的startNestedScroll方法。Helper中该方法的实现如下:

    //NestedScrollingChildHelper
    public boolean startNestedScroll(int axes) {
        if (hasNestedScrollingParent()) {
            // Already in progress
            return true;
        }
        if (isNestedScrollingEnabled()) {
            ViewParent p = mView.getParent();
            View child = mView;
            //通过while循环,不断的去判断是否有View的ParentView需要消耗这次滑动事件
            while (p != null) {
                //判断parent是否需要消耗
                if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) {
                    mNestedScrollingParent = p;
                    ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes);
                    //父View消耗滑动事件
                    return true;
                }
                if (p instanceof View) {
                    child = (View) p;
                }
                p = p.getParent();
            }
        }
       //循环结束,没有发现需要消耗的View
        return false;
    }

在这个方法中,所做的事儿只有一件,去循环遍历并且询问这个View的ParentView和ParentView的ParentView是否需要消耗这次事件,如果有消耗的返回true否则返回false,这里判断的方法使用了ViewParentCompat.onStartNestedScroll(p, child, mView, axes),这个方法实现很简单,里面仅仅是调用了我们传入的参数p的onStartNestedScroll方法,在我的事例中,p就是CoordinatorLayout,所以我们可以直接查看CoordinatorLayout中onStartNestedScroll方法的实现

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

        final int childCount = getChildCount();
        //仍然是遍历子View,判断是否有View需要消耗
        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();
            //判断behavior是否为空
            if (viewBehavior != null) {
                //获取View是否消耗滑动事件
                final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child, target,
                        nestedScrollAxes);
                handled |= accepted;

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

这里我们可以看到,当父View也就是CoordinatorLayout判断是否消耗滑动事件的方式也很简单,就是遍历自己的子View,如果子View有消耗就返回true,这里使用的是 “|=” 只要有子View需要接收便是true,接着在当前例子中,ImageView包裹在AppBarLayout,那么在这个函数遍历中,就会获取到AppBarLayout的Behavior,并且调用AppBarLayout的中Behavior的onStartNestedScroll方法,就是上面时序图的最后一个LifeLine,AppBarLayout的中Behavior的onStartNestedScroll实现如下:

 //AppBarLayout$Behavior
 @Override
        public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child,
                View directTargetChild, View target, int nestedScrollAxes) {
            // Return true if we're nested scrolling vertically, and we have scrollable children
            // and the scrolling view is big enough to scroll
            final boolean started = (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0
                    && child.hasScrollableChildren()
                    && parent.getHeight() - directTargetChild.getHeight() <= child.getHeight();

            if (started && mOffsetAnimator != null) {
                // Cancel any offset animation
                mOffsetAnimator.cancel();
            }

            // A new nested scroll has started so clear out the previous ref
            mLastNestedScrollingChildRef = null;

            return started;
        }

这里可以看到,在AppBarLayout BehavioronStar

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值