Android——嵌套滚动机制(第二篇)
暂时不允许转载哦!!!
前言:嵌套滚动机制并不常用,但是如果你感兴趣想要了解该机制那么你可以看我的文章了。暂时不允许转载哦!!!
嵌套滚动机制最典型的应用控件是Coordinatorlayout,该空间的Behaviour类的行为就是嵌套滚动机制的封装体现哦!!
本文分两部分进行阐述,(1)大概流程(2)具体源码
前面了解了嵌套滚动的大概流程,下面就来具体看看子view的帮助类究竟做了什么事情吧。最后结合前面的代码来看
下面是NestedScrollingChildHelper 这个类的具体源码。
子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; /* dxConsumed和dyConsumed 这两个参数一般不用管啊,具体含义:子view消费了的距离, 一般我们只关心,还有多少距离可以消费。dxUnconsumed和dyUnconsumed代表:还有多少距离可以消费 而dxUnconsumed和dyUnconsumed的值是怎么算的呢? 子view要滑动的距离 - 父控件消费的距离-dxConsumed或dyConsumed = dxUnconsumed或dyUnconsumed*/ 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()方法停止嵌套滚动机制就好啦呗。