NestedScrollingParent, NestedScrollingChild  详解

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,  这个后续在跟进吧 


你可能感兴趣的:(android,嵌套滑动)