NestedScrollingParent NestedScrollingChild 这是两个接口,  Android 就是通过这两个接口, 来实现 子View 与父View 之间的嵌套滑动
这样的嵌套滑动机制是在 Android 发布 Lollipop 之后提供的  不过同样在Support v7 中同样支持了 
同时 RecycleView  以及 Android 5.0 以上的系统原声View 大部分都已经支持 嵌套滑动了 
ok 了解个大概  下面来看看 具体的嵌套滑动 是怎样的: 想要理解 嵌套滑动 必须, 需要理解一下几个类(接口): NestedScrollingChild NestedScrollingParent NestedScrollingChildHelper NestedScrollingParentHelper

先来看  NestedScrollingChild 接口,  顾名思义, 这个是子View 应该实现 的接口:

先看源码 

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);}

去掉了 原来的注释, 加入点自己理解的注释 
在看看  NestedScrollingParentHelper 这个类, 这个类是一个辅助类,  先来看看 子View 如何继承 NestedScrollingChild 

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);    }}

可以看到基本接口里面的每个方法 都只要调用 mNestedScrollingChildHelper 中相应的方法; 下面来看看  NestedScrollingParentHelper 源码: 
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);    }}

从上面的注释 可以基本看到 嵌套滑动的基本逻辑:
下面来看看 父View 
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

通过以上的代码 注释 基本对 嵌套互动有了一个大致的基本了解
具体的还需要通过实战Demo,  这个后续在跟进吧 


更多相关文章

  1. Android/OPhone ListView中如何使用Button,让onClick和onItemClic
  2. android 多击事件的实现方法
  3. 如何在Android中获知屏幕打开或者关闭
  4. 自定义View系列教程08--滑动冲突的产生及其处理
  5. Android(安卓)打造RxBus2.x的全面详解
  6. 【Monkey】Android(安卓)Monkey autotest Tools
  7. android软键盘事件处理
  8. ADB命令大全之二
  9. 自定义View系列教程07--详解ViewGroup分发Touch事件

随机推荐

  1. android 开发常用网站
  2. Android Application 之 allowBackup 属
  3. Android之获取Android唯一ID
  4. Android 8.0 权限警告(not in privapp-per
  5. android web
  6. Android:Loader
  7. android toolchain is using Thread mode
  8. android邮件发送几种方式
  9. Android 禁用 APP 或四大组件
  10. Android:Android.bat批处理命令