这是一个用于实现子视图嵌套滚动的辅助类,并提供对Android 5.0以前版本的前兼容。ide
View要做为嵌套滚动中的Child,要在构造方法中实例化一个final的NestedScrollingChildHelper对象。该View中有一系列方法签名(即方法名和参数列表)和此类中相同的方法(这些方法实际来自于View实现的NestedScrollingChild接口),经过委托模式(delegate),这些方法的实际实现被委派给helper对象来完成。这提供了一种标准的结构化策略,来实现嵌套滚动。this
例如,下面是NestedScrollView中相关的代码:spa
public class NestedScrollView extends FrameLayout implements NestedScrollingParent, NestedScrollingChild { ...... private final NestedScrollingParentHelper mParentHelper; // 这里文档中特别强调须要final修饰,并且要在构造方法中实例化 private final NestedScrollingChildHelper mChildHelper; public NestedScrollView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); ...... mParentHelper = new NestedScrollingParentHelper(this); mChildHelper = new NestedScrollingChildHelper(this); ...... } // 下面9个方法来自于NestedScrollingChild接口,注意他们只是简单调用了mChildHelper中同名同参数列表的方法。 @Override public void setNestedScrollingEnabled(boolean enabled) { mChildHelper.setNestedScrollingEnabled(enabled); } @Override public boolean isNestedScrollingEnabled() { return mChildHelper.isNestedScrollingEnabled(); } @Override public boolean startNestedScroll(int axes) { return mChildHelper.startNestedScroll(axes); } @Override public void stopNestedScroll() { mChildHelper.stopNestedScroll(); } @Override public boolean hasNestedScrollingParent() { return mChildHelper.hasNestedScrollingParent(); } @Override public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow); } @Override public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); } @Override public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); } @Override public boolean dispatchNestedPreFling(float velocityX, float velocityY) { return mChildHelper.dispatchNestedPreFling(velocityX, velocityY); } ...... }
使用嵌套滚动功能的视图,应该始终使用ViewCompat, ViewGroupCompat 或者 ViewParentCompat中相关的兼容性静态方法。这保证了和Android 5.0以后的嵌套滚动视图之间互操做的正确性。code
下面来具体的看这九个方法在NestedScrollingChildHelper类中的实现:对象
使能或禁止Child的嵌套滚动。接口
public void setNestedScrollingEnabled(boolean enabled) { // 若是处于使能状态,有可能正在滚动,须要先中止。 if (mIsNestedScrollingEnabled) { // 至关于调用View.stopNestedScroll()。 // 这个方法若是View正在嵌套滚动,会中止嵌套滚动,反之无影响。 // 注意这里实际仍是会调用到NestedScrollingChildHelper.stopNestedScroll()。 ViewCompat.stopNestedScroll(mView); } // 这个标志位表明可否嵌套滚动。 mIsNestedScrollingEnabled = enabled; }
返回是否能够嵌套滚动。事件
// 若是这里返回true,说明相应的子视图能够嵌套滚动。 // 那么helper类会负责将滚动操做过程当中的相关数据传递给相关联的nested scrolling parent。 public boolean isNestedScrollingEnabled() { return mIsNestedScrollingEnabled; }
检查是否有关联的父视图,来接受嵌套滚动过程当中的事件。ci
// 注意这里的mNestedScrollingParent初始化时是null, // 是在下面的startNestedScroll(int axes)方法中找到的。 public boolean hasNestedScrollingParent() { return mNestedScrollingParent != null; }
开始一个新的嵌套滚动。文档
// 参数axes表明滚动轴,取ViewCompat#SCROLL_AXIS_HORIZONTAL and/or ViewCompat#SCROLL_AXIS_VERTICAL。 // 若是找到了嵌套滚动的parent view,而且对于当前手势使能了嵌套滚动,返回true。 public boolean startNestedScroll(int axes) { if (hasNestedScrollingParent()) { // 已经在嵌套滚动过程当中。 return true; } // 嵌套滚动功能使能状况下才能开始。 if (isNestedScrollingEnabled()) { ViewParent p = mView.getParent(); View child = mView; // 依次找父视图,直到找到第一个接受嵌套滚动的父视图。 while (p != null) { // 实际调用的是NestedScrollingParent中的 // onStartNestedScroll(View child, View target, int nestedScrollAxes)方法。 // 返回父视图是否接受此嵌套滚动操做。 if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) { mNestedScrollingParent = p; // 这里实际会调用NestedScrollingParentHelper.onNestedScrollAccepted(View child, View target, int axes)方法。 ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes); return true; } if (p instanceof View) { child = (View) p; } p = p.getParent(); } } return false; }
中止嵌套滚动get
public void stopNestedScroll() { if (mNestedScrollingParent != null) { // 实际调用的是NestedScrollingParentHelper.onStopNestedScroll(View target)。 ViewParentCompat.onStopNestedScroll(mNestedScrollingParent, mView); mNestedScrollingParent = null; } }
将嵌套滚动过程当中的一步分发给当前的nested scrolling parent。
// 返回true表明该事件被分发(即父视图消费任意的嵌套滚动事件),反之则是没法被分发。 public boolean dispatchNestedScroll( int dxConsumed, // 滚动中被此view消费的水平距离 int dyConsumed, // 滚动中被此view消费的垂直距离 int dxUnconsumed, // 滚动中未被此view消费的水平距离 int dyUnconsumed, // 滚动中未被此view消费的垂直距离 int[] offsetInWindow // 滚动先后此view的坐标偏移量 ) { if (isNestedScrollingEnabled() && mNestedScrollingParent != null) { if (dxConsumed != 0 || dyConsumed != 0 || dxUnconsumed != 0 || dyUnconsumed != 0) { int startX = 0; int startY = 0; if (offsetInWindow != null) { mView.getLocationInWindow(offsetInWindow); startX = offsetInWindow[0]; startY = offsetInWindow[1]; } // 此方法来自NestedScrollingParent.onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed); // 具体实现由父视图完成。 ViewParentCompat.onNestedScroll(mNestedScrollingParent, mView, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); if (offsetInWindow != null) { mView.getLocationInWindow(offsetInWindow); // 偏移量是滚动后的位置减滚动前的位置 offsetInWindow[0] -= startX; offsetInWindow[1] -= startY; } return true; } else if (offsetInWindow != null) { // No motion, no dispatch. Keep offsetInWindow up to date. offsetInWindow[0] = 0; offsetInWindow[1] = 0; } } return false; }