Android——嵌套滚动机制(第二篇)

暂时不允许转载哦!!!
前言:嵌套滚动机制并不常用,但是如果你感兴趣想要了解该机制那么你可以看我的文章了。暂时不允许转载哦!!!
嵌套滚动机制最典型的应用控件是Coordinatorlayout,该空间的Behaviour类的行为就是嵌套滚动机制的封装体现哦!!
本文分两部分进行阐述,(1)大概流程(2)具体源码

前面了解了嵌套滚动的大概流程,下面就来具体看看子view的帮助类究竟做了什么事情吧。最后结合前面的代码来看
下面是NestedScrollingChildHelper 这个类的具体源码。
Android——嵌套滚动机制(第二篇)
子view调用startNestedScroll()方法结束后,会在action_move事件中调用dispatchNestedPreScroll()这个方法,而这个方法做的事情可以看Helper的源码就明白了
/*首先子View通过getLocalInWindow(offsetInWindow)方法获取当前子view所在的位置的X轴数值和Y轴数值,
储存在一个叫做offsetInWindow的数组里面,然后把子View要滚动的距离(dy,dx)告诉了父控件,如果父控件消费了距离,
父控件会把消费的距离储存在consumed数组中,通过这个数组告诉回 子View:"我消费了这么多的距离"而父控件的移动肯定会改变子View的位置 
所以子View再次通过getLocalInWindow(offsetInWindow)方法获取当前子view所在的位置的X轴数值,并且保存起来*/
View mView;
ViewParent mNestedScrollingParent;
int[] mTempNestedScrollConsumed;

public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
    /*首先,还是先判断下 自定义View是否支持嵌套滑动,是否找到了响应滑动的父View*/
    if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
        /*dx,dy代表水平和垂直方向的滑动距离,不等于0,代表有滑动产生了*/
        if (dx != 0 || dy != 0) {
            int startX = 0;
            int startY = 0;
            // 获取 当前自定义View 的初始位置。通过数组offsetInWindow来保存数值
            if (offsetInWindow != null) {
                mView.getLocationInWindow(offsetInWindow);
                startX = offsetInWindow[0];//水平方向的坐标
                startY = offsetInWindow[1];//垂直方向的坐标
            }

            // 初始化一个长度为2的数组,用于保存父View消费的滑动距离  
            if (consumed == null) {
                if (mTempNestedScrollConsumed == null) {
                    mTempNestedScrollConsumed = new int[2];
                }
                consumed = mTempNestedScrollConsumed;
            }
            consumed[0] = 0;//初始值为0,代表还没有被父View消费
            consumed[1] = 0;//初始值为0,代表还没有被父View消费

            // 这里回调 父View  onNestedPreScroll 方法,  
            // View 可能会消费一定的滑动事件,  
            // 如果父View消费了 那么 父View消费的距离(水平方向的距离和垂直方向的距离)都会保存在consumed数组里面
            ViewParentCompat.onNestedPreScroll(mNestedScrollingParent, mView, dx, dy, consumed);
            
            /*View消费了一些距离,就是说父View移动了一些距离,父View移动那么包含在父View里面的子view也会移动
            * 所以再次获取 子Viwe的坐标,从而计算子view的偏移量,把偏移量保存在数组中*/
            if (offsetInWindow != null) {
                // 计算出  父view 消费 滑动事件后,  导致 子View 的移动距离
                mView.getLocationInWindow(offsetInWindow);  
                // 保存 子View 的移动距离  
                offsetInWindow[0] -= startX;
                offsetInWindow[1] -= startY;
            }
            // 如果  xy 方向 上 有不为0 的表示消费了 则返回true  
            return consumed[0] != 0 || consumed[1] != 0;
        } else if (offsetInWindow != null) {
            //代表 没有滑动,
            offsetInWindow[0] = 0;
            offsetInWindow[1] = 0;
        }
    }
    return false;
}
同样还是在action_move事件中 子View在调用dispatchNestedPreScroll()方法后,又会调用dispatchNestedScroll()方法,询问父View:我还有未消费完的滑动,你是否需要继续消费完啊。
所以,一般就是一问两消费(1)startNestedScroll()方法的时候询问父View,是否响应我的滑动事件(2)子View通过dispatchNestedPreScroll()方法传递:"有多少滑动" 给父View,父View在onNestedPreScroll()回调方法中消费滑动,并且通过consumed数组告诉子view:"我消费了多少"(3)子view通过计算后,又通过dispatchNestedScroll()方法告诉父View“我还剩下多少未消费的滑动”,父View可以在onNestedScroll()回调中消费这些滑动。
上面这个流程中,父View有两次消费滑动的地方,这两次父View都是 可能消费了,又可能没有消费,反正到最后,如果所有的滑动都被父控件消费完了,那么自定义View就不会滑动了,都消费完了还滑动什么啊?如果父View两次消费完后还是有剩余,那么子view就自己进行消费呗。
View mView;
ViewParent mNestedScrollingParent;

/* dxConsumeddyConsumed 这两个参数一般不用管啊,具体含义:子view消费了的距离,
    一般我们只关心,还有多少距离可以消费。dxUnconsumeddyUnconsumed代表:还有多少距离可以消费
dxUnconsumeddyUnconsumed的值是怎么算的呢?
view要滑动的距离 - 父控件消费的距离-dxConsumeddyConsumed = dxUnconsumeddyUnconsumed*/

public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
                                    int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
    //首先,判断自定义View是否开启了嵌套滑动功能,以及是否找到了 响应这次滑动的父控件
    if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
        /*不管是消费了多少还是还有多少没消费完,都是要获取一下当前自定义View的位置*/
        if (dxConsumed != 0 || dyConsumed != 0 || dxUnconsumed != 0 || dyUnconsumed != 0) {
            int startX = 0;
            int startY = 0;
            //先判断用来保存坐标的数组是否不为空
            if (offsetInWindow != null) {
                //获取坐标
                mView.getLocationInWindow(offsetInWindow);
                startX = offsetInWindow[0];//保存X轴的坐标
                startY = offsetInWindow[1];//保存Y轴的坐标
            }
            
            //这里父View可能又会再次消费一些滑动,或者不消费。都有可能的
            // View 回调 onNestedScroll 方法, 该方法 主要是会处理  dxUnconsumed dyUnconsumed 数据  
            ViewParentCompat.onNestedScroll(mNestedScrollingParent, mView, dxConsumed,
                    dyConsumed, dxUnconsumed, dyUnconsumed);
            /*如果父View又消费了,那么肯定还是要获取一下当前自定义View的坐标
            然后计算出 自定义View的偏移量。*/
            if (offsetInWindow != null) {
                // 计算 子View的移动距离  
                mView.getLocationInWindow(offsetInWindow);
                offsetInWindow[0] -= startX;
                offsetInWindow[1] -= startY;
            }
            return true;//返回true
        } else if (offsetInWindow != null) {
            // No motion, no dispatch. Keep offsetInWindow up to date.  
            offsetInWindow[0] = 0;
            offsetInWindow[1] = 0;
        }
    }
    return false;
}
好了,子View中的嵌套滚动机制,就完成了,接下来是在在我们需要的时候调用一下 stopNestedScroll()方法停止嵌套滚动机制就好啦呗。