先看源码
public interface NestedScrollingChild { /** * 设置嵌套滑动是否可用 * * @param enabled */ public void setNestedScrollingEnabled(boolean enabled); /** * 嵌套滑动是否可用 * * @return */ public boolean isNestedScrollingEnabled(); /** * 开始嵌套滑动, * * @param axes 表示方向 有一下两种值 * ViewCompat.SCROLL_AXIS_HORIZONTAL 横向哈东 * ViewCompat.SCROLL_AXIS_VERTICAL 纵向滑动 */ public boolean startNestedScroll(int axes); /** * 停止嵌套滑动 */ public void stopNestedScroll(); /** * 是否有父View 支持 嵌套滑动, 会一层层的网上寻找父View * @return */ public boolean hasNestedScrollingParent(); /** * 在处理滑动之后 调用 * @param dxConsumed x轴上 被消费的距离 * @param dyConsumed y轴上 被消费的距离 * @param dxUnconsumed x轴上 未被消费的距离 * @param dyUnconsumed y轴上 未被消费的距离 * @param offsetInWindow view 的移动距离 * @return */ public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow); /** * 一般在滑动之前调用, 在ontouch 中计算出滑动距离, 然后 调用改 方法, 就给支持的嵌套的父View 处理滑动事件 * @param dx x 轴上滑动的距离, 相对于上一次事件, 不是相对于 down事件的 那个距离 * @param dy y 轴上滑动的距离 * @param consumed 一个数组, 可以传 一个空的 数组, 表示 x 方向 或 y 方向的事件 是否有被消费 * @param offsetInWindow 支持嵌套滑动到额父View 消费 滑动事件后 导致 本 View 的移动距离 * @return 支持的嵌套的父View 是否处理了 滑动事件 */ public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow); /** * * @param velocityX x 轴上的滑动速度 * @param velocityY y 轴上的滑动速度 * @param consumed 是否被消费 * @return */ public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed); /** * * @param velocityX x 轴上的滑动速度 * @param velocityY y 轴上的滑动速度 * @return */ public boolean dispatchNestedPreFling(float velocityX, float velocityY); }
public class Child extends LinearLayout implements android.support.v4.view.NestedScrollingChild { public static final String TAG = "Child"; private NestedScrollingChildHelper mNestedScrollingChildHelper; public Child(Context context, AttributeSet attrs) { super(context, attrs); mNestedScrollingChildHelper = new NestedScrollingChildHelper(this); } @Override public void setNestedScrollingEnabled(boolean enabled) { mNestedScrollingChildHelper.setNestedScrollingEnabled(enabled); } @Override public boolean isNestedScrollingEnabled() { return mNestedScrollingChildHelper.isNestedScrollingEnabled(); } @Override public boolean startNestedScroll(int axes) { return mNestedScrollingChildHelper.startNestedScroll(axes); } @Override public void stopNestedScroll() { mNestedScrollingChildHelper.stopNestedScroll(); } @Override public boolean hasNestedScrollingParent() { return mNestedScrollingChildHelper.hasNestedScrollingParent(); } @Override public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { return mNestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow); } @Override public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { return mNestedScrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); } @Override public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { return mNestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); } @Override public boolean dispatchNestedPreFling(float velocityX, float velocityY) { return mNestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY); } }
public class NestedScrollingChildHelper { /** * 嵌套滑动的ziView */ private final View mView; /** * 支持 嵌套滑动的 父View */ private ViewParent mNestedScrollingParent; /** * 是否支持 嵌套滑动 */ private boolean mIsNestedScrollingEnabled; /** * 是否被消费的一个中变变量 */ private int[] mTempNestedScrollConsumed; public NestedScrollingChildHelper(View view) { mView = view; } public void setNestedScrollingEnabled(boolean enabled) { if (mIsNestedScrollingEnabled) { ViewCompat.stopNestedScroll(mView); } mIsNestedScrollingEnabled = enabled; } public boolean isNestedScrollingEnabled() { return mIsNestedScrollingEnabled; } public boolean hasNestedScrollingParent() { return mNestedScrollingParent != null; } /** * 开始嵌套滑动 * @param axes 滑动方向 * @return 是否有父view 支持嵌套滑动 */ public boolean startNestedScroll(int axes) { if (hasNestedScrollingParent()) { // 如果已经找到 了嵌套滑动的父View // Already in progress return true; } if (isNestedScrollingEnabled()) { ViewParent p = mView.getParent(); View child = mView; // 递归向上寻找 支持 嵌套滑动的父View while (p != null) { // 这里会调用 父View 的NestedScrollingParent.onStartNestedScroll 方法 // 如果 父View 返回 false 则再次向上寻找父View , 直到找到支持的fuView if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) { mNestedScrollingParent = p; // 这里回调 父View 的onNestedScrollAccepted 方法 表示开始接收 嵌套滑动 ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes); return true; } if (p instanceof View) { child = (View) p; } p = p.getParent(); } } // 没有找到 支持嵌套滑动的父View 则返回false return false; } /** * 停止 嵌套滑动, 一般 在 cancel up 事件中 调用 */ public void stopNestedScroll() { if (mNestedScrollingParent != null) { ViewParentCompat.onStopNestedScroll(mNestedScrollingParent, mView); mNestedScrollingParent = null; } } /** * * @param dxConsumed x 上被消费的距离 * @param dyConsumed y 上被消费的距离 * @param dxUnconsumed x 上未被消费的距离 * @param dyUnconsumed y 上未被消费的距离 * @param offsetInWindow 子View 位置的移动距离 * @return */ public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { 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]; } // 父View 回调 onNestedScroll 方法, 该放在 主要会处理 dxUnconsumed dyUnconsumed 数据 ViewParentCompat.onNestedScroll(mNestedScrollingParent, mView, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); if (offsetInWindow != null) { // 计算 子View的移动距离 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; } /** * * consumed[0] 为0 时 表示 x 轴方向上事件 没有被消费 * 不为0 时 表示 x 轴方向上事件 被消费了, 值表示 被消费的滑动距离 * consumed[1] 为0 时 表示 y 轴方向上事件 没有被消费 * 不为0 时 表示 y 轴方向上事件 被消费了, 值表示 被消费的滑动距离 * * * @param dx * @param dy * @param consumed * @param offsetInWindow * @return */ public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { if (isNestedScrollingEnabled() && mNestedScrollingParent != null) { if (dx != 0 || dy != 0) { int startX = 0; int startY = 0; // 获取 当前View 初始位置 if (offsetInWindow != null) { mView.getLocationInWindow(offsetInWindow); startX = offsetInWindow[0]; startY = offsetInWindow[1]; } // 初始化是否被消费数据 if (consumed == null) { if (mTempNestedScrollConsumed == null) { mTempNestedScrollConsumed = new int[2]; } consumed = mTempNestedScrollConsumed; } consumed[0] = 0; consumed[1] = 0; // 这里回调 父View 的 onNestedPreScroll 方法, // 父View 或许会处理 相应的滑动事件, // 如果 处理了 则 consumed 会被赋予 相应的值 ViewParentCompat.onNestedPreScroll(mNestedScrollingParent, mView, dx, dy, consumed); if (offsetInWindow != null) { // 父View 处理了相应的滑动, 很可能导致 子View 的位置的移动 // 这里计算出 父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; } public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { if (isNestedScrollingEnabled() && mNestedScrollingParent != null) { return ViewParentCompat.onNestedFling(mNestedScrollingParent, mView, velocityX, velocityY, consumed); } return false; } public boolean dispatchNestedPreFling(float velocityX, float velocityY) { if (isNestedScrollingEnabled() && mNestedScrollingParent != null) { return ViewParentCompat.onNestedPreFling(mNestedScrollingParent, mView, velocityX, velocityY); } return false; } public void onDetachedFromWindow() { ViewCompat.stopNestedScroll(mView); } public void onStopNestedScroll(View child) { ViewCompat.stopNestedScroll(mView); } }
public class Parent extends LinearLayout implements NestedScrollingParent { public static final String TAG = "Parent"; private NestedScrollingParentHelper mNestedScrollingParentHelper; public Parent(Context context, AttributeSet attrs) { super(context, attrs); mNestedScrollingParentHelper = new NestedScrollingParentHelper(this); } /** * 回调开始滑动 * @param child 该父VIew 的子View * @param target 支持嵌套滑动的 VIew * @param nestedScrollAxes 滑动方向 * @return 是否支持 嵌套滑动 */ @Override public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { return true; } @Override public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) { mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes); } @Override public void onStopNestedScroll(View target) { mNestedScrollingParentHelper.onStopNestedScroll(target); } /** * 这里 主要处理 dyUnconsumed dxUnconsumed 这两个值对应的数据 * @param target * @param dxConsumed * @param dyConsumed * @param dxUnconsumed * @param dyUnconsumed */ @Override public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { LogUtil.d(TAG, "onNestedScroll target = " + target + " , dxConsumed = " + dxConsumed + " , dyConsumed = " + dyConsumed + " , dxUnconsumed = " + dxUnconsumed + " , dyUnconsumed = " + dyUnconsumed); } /** * 这里 传来了 x y 方向上的滑动距离 * 并且 先与 子VIew 处理滑动, 并且 consumed 中可以设置相应的 除了的距离 * 然后 子View 需要更具这感觉, 来处理自己滑动 * * @param target * @param dx * @param dy * @param consumed */ @Override public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { consumed[1] = dy; LogUtil.d(TAG, "onNestedPreScroll dx = " + dx + " dy = " + dy); } @Override public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { return false; } @Override public boolean onNestedPreFling(View target, float velocityX, float velocityY) { return false; } @Override public int getNestedScrollAxes() { return mNestedScrollingParentHelper.getNestedScrollAxes(); } }
子view | 父view |
startNestedScroll | onStartNestedScroll、onNestedScrollAccepted |
dispatchNestedPreScroll | onNestedPreScroll |
dispatchNestedScroll | onNestedScroll |
stopNestedScroll | onStopNestedScroll |