這是一個用於實現子視圖嵌套滾動的輔助類,並提供對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; }