NestedScrollingParent 和 NestedScrollingChild 实现嵌套滚动

1、NestedScrollingParent 方法注释

/**
 * 该接口应该由{@link android.view.ViewGroup ViewGroup}子类实现
 * 希望支持嵌套子视图委托的滚动操作。
 */
public interface NestedScrollingParent {
    /**
     * 有嵌套滑动,询问该父View是否接受嵌套滑动
     *
     * @param child 直接子类(层级 child >= target)
     * @param target 发起嵌套滚动的视图
     * @param axes 滚动方向 {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},
     *								{@link ViewCompat#SCROLL_AXIS_VERTICAL}
     * @return 是否接受嵌套滑动
     */
    boolean onStartNestedScroll(@NonNull View child, @NonNull View target, @ScrollAxis int axes);
    
    /**
     * 响应嵌套滚动的成功声明(接受嵌套滑动)
     * onStartNestedScroll返回true,该函数被调用
     */
    void onNestedScrollAccepted(@NonNull View child, @NonNull View target, @ScrollAxis int axes);
    
    /**
     * 停止嵌套滑动
     *
     * @param target 具体嵌套滑动的那个子类
     */
    void onStopNestedScroll(@NonNull View target);
    
    /**
     * 响应进行中的嵌套滚动(嵌套滑动的子View在滑动之后传递过来的滑动情况)
     *
     * @param target 嵌套滑动的子View
     * @param dxConsumed 水平方向target滑动的距离
     * @param dyConsumed 竖直方向target滑动的距离
     * @param dxUnconsumed 水平方向target未滑动的距离
     * @param dyUnconsumed 竖直方向target未滑动的距离
     */
    void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
            int dxUnconsumed, int dyUnconsumed);
    
    /**
     * 在嵌套滑动的子View未滑动之前,通知准备(响应target)滑动的情况
     *
     * @param target 嵌套滑动的View
     * @param dx 水平方向target想要移动的距离
     * @param dy 竖直方向target想要移动的距离
     * @param consumed 输出结果,告诉子View当前父View消耗的距离,让子View做出相应调整
     *                 consumed[0] 水平消耗的距离
     *                 consumed[1] 垂直消耗的距离。
     */
    void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed);
    
    /**
     * 嵌套滑动的子View在fling之后报告过来的fling情况
     *
     * @param target 
     * @param velocityX 水平方向速度
     * @param velocityY 垂直方向速度
     * @param consumed
     * @return 父View是否消耗了fling
     */
    boolean onNestedFling(@NonNull View target, float velocityX, float velocityY, boolean consumed);
    
    /**
     * 在嵌套滑动的子View未fling之前告诉过来的准备fling的情况
     *
     * @param target 嵌套滑动的View
     * @param velocityX 水平方向速度
     * @param velocityY 垂直方向速度
     * @return 父View是否消耗fling
     */
    boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY);
    
    /**
     * 获得滚动方向
     *
     * @return 返回滚动轴
     * @see ViewCompat#SCROLL_AXIS_HORIZONTAL
     * @see ViewCompat#SCROLL_AXIS_VERTICAL
     * @see ViewCompat#SCROLL_AXIS_NONE
     */
    int getNestedScrollAxes();
}

2、NestedScrollingChild 方法注释

/**
 * 该接口应该由{@link android.view.View View}子类实现,这些子类希望
 * 支持将嵌套滚动操作分配给协作父节点
 */
public interface NestedScrollingChild {
    
    /**
     * 设置是否启用嵌套滑动
     *
     * @param enabled 是否启用嵌套滚动
     */
    void setNestedScrollingEnabled(boolean enabled);
    
    /**
     * 是否允许嵌套滑动
     *
     * @return 是否允许嵌套滑动
     */
    boolean isNestedScrollingEnabled();

    /**
     * 通知开始嵌套滑动流程,调用该函数的时候会去找嵌套滑动的父控件
     * 如果找到了父控件并且父控件说可以滑动就返回true,否则返回false
     * (一般在ACTION_DOWN 事件类型中调用)
     *
     * @param axes 支持嵌套滑动轴:水平方向、垂直方向、或者不指定方向
     * @return 父控件是否同意嵌套滑动。(onStartNestedScroll返回值)
     */
    boolean startNestedScroll(@ScrollAxis int axes);
    
    /**
     * 停止嵌套滑动(一般在 ACTION_UP 中调用)
     */
    void stopNestedScroll();
    
    /**
     * 如果该视图具有嵌套滚动父视图,则返回true。
     */
    boolean hasNestedScrollingParent();
 
    /**
     * 分发嵌套滑动
     */
    boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
            int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow);
    
    /**
     * 在嵌套滑动的子View滑动之前,告诉父View滑动额距离,让父View做相应的处理
     *
     * @param dx 告诉父View水平方向需要滑动的距离
     * @param dy 告诉父View垂直方向需要滑动的距离
     * @param consumed 如果不为NULL,则告诉子View父View的滑动情况:
     *                 consumed[0] 父View告诉子View水平方向滑动的距离(dx)
     *                 consumed[1] 父View告诉子View垂直方向滑动的距离(dy)
     * @param offsetInWindow 可选,length=2的数组,如果父View滑动导致子View的窗口发生了变化(子View的位置发生变化)
     *                       该参数返回x(offsetInWindow[0]),y(offsetInWindow[1])方向的变化
     *                       如果你记录了手指最后的位置,需要根据参数offsetInWindow计算偏移量,
     *                       才能保证下一次的touch事件的计算是正确的。
     * @return true 父View滑动了;false 父View没有滑动。
     */
    boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,
            @Nullable int[] offsetInWindow);
    
    /**
     * 在嵌套滑动的子View fling之后在调用该函数向父View汇报fling情况
     *
     * @param velocityX 水平方向速度
     * @param velocityY 垂直方向速度
     * @param consumed  true:子View fling了,false:子View没有 fling
     * @return true 父View fling了
     */
    boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);
    
    /**
     * 在嵌套滑动的子View fling之前告诉父View fling情况
     *
     * @param velocityX 水平方向的速度
     * @param velocityY 垂直方向的速度
     * @return true: 父View fling了
     */
    boolean dispatchNestedPreFling(float velocityX, float velocityY);
}

NestedScrollingParentNestedScrollingChild 方法看似很多,但是总结起来可以看到

Child中的方法和Parent方法是存在对应关系的,在Child接口中对Scroll和Fling事件进行分发,在Parent接口中对Scroll和Fling事件进行响应。

  • Scroll 相关:
    • Child 接口中分发scroll:dispatchNestedPreScroll()dispatchNestedScroll()
    • Parent 接口中响应scroll:onNestedPreScroll()onNestedScroll()
  • Fling 相关
    • Child 接口中分发fling:dispatchNestedPreFling()dispatchNestedFling()
    • Parent 接口中响应fling:onNestedPreFling()onNestedFling()

3、NestedScrollingChildHelper 和 NestedScrollingParentHelper

NestedScrollingChildHelper 和 NestedScrollingParentHelper 是帮助我们实现嵌套滑动的辅助类。

NestedScrollingChild 和 NestedScrollingChildHelper 中的方法是一一对应的,如果需要通知Parent实现嵌套滑动,需要借助 NestedScrollingChildHelper 中的方法。

同理 NestedScrollingParent 和 NestedScrollingParentHelper 中的方法也是一一对应的,也需要调用NestedScrollingParentHelper 中的方法,通知Child。

下面通过两个具体的例子加深理解。

4、实现RecyclerView 下拉伸缩顶部图片

效果图:

代码实现:

public class NestedScrollingView extends ViewGroup implements NestedScrollingParent {

    /**
     * ZoomView
     */
    private View mZoomView;

    /**
     * recyclerView
     */
    private View mRecyclerView;

    /**
     * ZoomView高度
     */
    private int mZoomHeight;

    /**
     * ZoomView最大高度
     */
    private int mMaxHeight;

    /**
     * ContentView回弹Top
     */
    private int mNormalTop;

    /**
     * ZoomView宽高比
     */
    private float mRatio = 1.8f;

    /**
     * RecyclerView 距离顶部位置
     */
    private int mTop;

    private boolean isLayout;

    private ValueAnimator mValueAnimator;

    private int mLastValue;

    private NestedScrollingParentHelper mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);

    public NestedScrollingView(Context context) {
        this(context, null);
    }

    public NestedScrollingView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public void setZoomHeight(int zoomHeight) {
        mZoomHeight = zoomHeight;
        mNormalTop = mZoomHeight / 2;
        mMaxHeight = (int) (mZoomHeight * mRatio);
        mTop = mNormalTop;
    }

    public void setView(View zoomView, View recyclerView) {
        this.mZoomView = zoomView;
        this.mRecyclerView = recyclerView;
        requestLayout();
    }

    public void setMarginTop(int marginTop) {
        this.mNormalTop = marginTop;
        requestLayout();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (mZoomView != null) {
            measureChild(mZoomView, widthMeasureSpec, heightMeasureSpec);
        }
        if (mRecyclerView != null) {
            measureChild(mRecyclerView, widthMeasureSpec, heightMeasureSpec);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (mZoomView != null && mRecyclerView != null) {
            if (!isLayout) {
                isLayout = true;
                mRecyclerView.layout(0, mTop,
                        mRecyclerView.getMeasuredWidth(), mTop + mRecyclerView.getMeasuredHeight());

            }
            mZoomView.layout(mZoomView.getLeft(), 0,
                    mZoomView.getLeft() + mZoomView.getMeasuredWidth(),
                    mZoomView.getTop() + mZoomView.getMeasuredHeight());
        }
    }

    @Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
        return mZoomView != null && mRecyclerView != null;
    }

    @Override
    public void onNestedScrollAccepted(View child, View target, int axes) {
        mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes);
        cancelAnim();
    }

    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        super.onNestedPreScroll(target, dx, dy, consumed);

        mTop = mRecyclerView.getTop();

        // 向上滑动
        if (dy > 0 && mTop > 0) {
            // 告诉RecyclerView,父布局消耗的距离。否则RecyclerView的item会跟随滚动
            consumed[1] = dy;
            mRecyclerView.offsetTopAndBottom(-dy);
        } else if (dy < 0) {
            // recyclerView是否滚动到顶部,不能继续下拉
            boolean scrollTop = !mRecyclerView.canScrollVertically(-1);
            if (scrollTop && mTop < mMaxHeight) {
                mRecyclerView.offsetTopAndBottom(-dy);
                consumed[1] = dy;
            }
        }

        // 纠正偏移
        if (mTop < 0) {
            mRecyclerView.offsetTopAndBottom(-mTop);
        } else if (mTop > mMaxHeight) {
            mRecyclerView.offsetTopAndBottom(mMaxHeight - mTop);
        }

        if (mTop > mNormalTop) {
            zoomAnim();
        }

    }

    @Override
    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
        if (mTop == 0){
            return super.onNestedPreFling(target, velocityX, velocityY);
        }else{
            // recyclerView没有滑倒顶部,禁用Fling
            return true;
        }
    }

    @Override
    public void onStopNestedScroll(View child) {
        if (mTop > mNormalTop) {
            resetAnim();
        }
        mNestedScrollingParentHelper.onStopNestedScroll(child);
    }

    /**
     * ZoomView动画
     */
    private void zoomAnim(){
        int zoomDy = (int) ((mTop - mNormalTop) * getUnit());
        ViewGroup.LayoutParams lp = mZoomView.getLayoutParams();
        lp.height = mZoomHeight + zoomDy;
        mZoomView.setLayoutParams(lp);
    }

    /**
     * 复位动画
     */
    private void resetAnim() {
        mLastValue = mTop;
        mValueAnimator = ValueAnimator.ofInt(mTop, mNormalTop);
        mValueAnimator.setDuration(250L);
        mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int value = (int) animation.getAnimatedValue();
                int offset = value - mLastValue;
                mRecyclerView.offsetTopAndBottom(offset);
                mLastValue = value;

                mTop = mRecyclerView.getTop();
                zoomAnim();
            }
        });
        mValueAnimator.start();
    }

    /**
     * 清除动画
     */
    private void cancelAnim() {
        if (mValueAnimator != null && mValueAnimator.isRunning()) {
            mValueAnimator.cancel();
        }
    }

    /**
     * 图片伸缩量 / recyclerView位移量
     *
     * @return recyclerView移动一个像素,图片需要拉伸的像素
     */
    private double getUnit(){
        return 1.0 * (mMaxHeight - mZoomHeight) / (mMaxHeight - mNormalTop);
    }

}

布局文件:

<com.example.test.nestedscroll.NestedScrollingView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#009688"
    android:id="@+id/nested_scrolling"
    >

    <ImageView
        android:id="@+id/image_view"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:scaleType="centerCrop"
        android:src="@mipmap/img4" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

com.example.test.nestedscroll.NestedScrollingView>

Activity代码:

        mScrollingView.setZoomHeight(ScreenUtil.dp2px(200));
        mScrollingView.setView(mImageView, mRecyclerView);

RecyclerView 已经实现了NestedScrollingChild 所以我们只需要对ParentView操作就行了。

这里面不能很好的体现NestedScrollingChildHelper 和 NestedScrollingParentHelper作用,下面一个例子可以很好的体现它们的作用。

5、实现ChildView拖动ParentView滚动

NestedScrollingParent 和 NestedScrollingChild 实现嵌套滚动_第1张图片
ChildView 滑动到 ParentView 的边缘的时候,继续拖动ChildView,父View需要响应滑动事件。

NestedChildView实现如下:

public class NestedChildView extends View implements NestedScrollingChild {

    private static final String TAG = "NestedChildView";

    private float mDownX; // 手指第一次落下的x位置
    private float mDownY; // 手指第一次落下的y位置

    /**
     * 接受父View消耗的值
     */
    private int[] consumed = new int[2]; // 消耗的距离
    private int[] offsetInWindow = new int[2]; // 窗口偏移

    private NestedScrollingChildHelper mChildHelper;

    public NestedChildView(Context context) {
        this(context,null);
    }

    public NestedChildView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public NestedChildView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mChildHelper = new NestedScrollingChildHelper(this);
        // 开启嵌套滑动
        setNestedScrollingEnabled(true);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        int action = event.getAction();

        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mDownX = x;
                mDownY = y;
                //开始滑动时,通知父View
                startNestedScroll(ViewCompat.SCROLL_AXIS_HORIZONTAL
                        | ViewCompat.SCROLL_AXIS_VERTICAL);
                break;
            case MotionEvent.ACTION_MOVE:
                int dx = (int) (x - mDownX);
                int dy = (int) (y - mDownY);

                //分发触屏事件给父类处理
                if (dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow)){
                    //减掉父类消耗的距离
                    dx -= consumed[0];
                    dy -= consumed[1];
                }
                // 对Left和Right进行偏移
                offsetLeftAndRight(dx);
                // 对Top和Bottom进行偏移
                offsetTopAndBottom(dy);
                break;
            case MotionEvent.ACTION_UP:
                stopNestedScroll();
                break;
        }
        return true;
    }

    /**
     * 设置是否允许嵌套滑动
     *
     * @param enabled
     */
    @Override
    public void setNestedScrollingEnabled(boolean enabled) {
        Log.i(TAG,"设置是否允许嵌套欢动:" + enabled);
        mChildHelper.setNestedScrollingEnabled(enabled);
    }

    /**
     * 是否允许嵌套滑动
     *
     * @return
     */
    @Override
    public boolean isNestedScrollingEnabled() {
        Log.i(TAG,"获得是否允许嵌套欢动:" + mChildHelper.isNestedScrollingEnabled());
        return mChildHelper.isNestedScrollingEnabled();
    }

    /**
     * 通知开始嵌套滑动流程,调用该函数的时候会去找嵌套滑动的父控件
     * 如果找到了父控件并且父控件说可以滑动就返回true,否则返回false
     * (一般在ACTION_DOWN 事件类型中调用)
     *
     * @param axes 支持嵌套滑动轴:水平方向、垂直方向、或者不指定方向
     * @return
     */
    @Override
    public boolean startNestedScroll(int axes) {
        Log.i(TAG,"startNestedScroll 开始滚动,通知父View");
        return mChildHelper.startNestedScroll(axes);
    }

    /**
     * 停止嵌套滑动(一般在 ACTION_UP 中调用)
     */
    @Override
    public void stopNestedScroll() {
        Log.i(TAG,"stopNestedScroll 停止嵌套滚动");
        mChildHelper.stopNestedScroll();
    }

    /**
     * 是否有嵌套滑动对应的父控件
     *
     * @return
     */
    @Override
    public boolean hasNestedScrollingParent() {
        Log.i(TAG,"是否有嵌套滚动的父View:" + mChildHelper.hasNestedScrollingParent());
        return mChildHelper.hasNestedScrollingParent();
    }

    @Override
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow) {
        Log.i(TAG,"分发嵌套滑动 dispatchNestedScroll  dxC:" + dxConsumed + "  dyC:" + dyConsumed + "  dxU:" + dxUnconsumed + "  dyU:" + dyUnconsumed + "  oI:" + offsetInWindow);
        return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
    }

    /**
     * 在嵌套滑动的子View滑动之前,告诉父View滑动额距离,让父View做相应的处理
     *
     * @param dx 告诉父View水平方向需要滑动的距离
     * @param dy 告诉父View垂直方向需要滑动的距离
     * @param consumed 如果不为NULL,则告诉子View父View的滑动情况:
     *                 consumed[0] 父View告诉子View水平方向滑动的距离(dx)
     *                 consumed[1] 父View告诉子View垂直方向滑动的距离(dy)
     * @param offsetInWindow 可选,length=2的数组,如果父View滑动导致子View的窗口发生了变化(子View的位置发生变化)
     *                       该参数返回x(offsetInWindow[0]),y(offsetInWindow[1])方向的变化
     *                       如果你记录了手指最后的位置,需要根据参数offsetInWindow计算偏移量,
     *                       才能保证下一次的touch事件的计算是正确的。
     * @return true 父View滑动了;false 父View没有滑动。
     */
    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow) {
        Log.i(TAG,"准备分发 dispatchNestedPreScroll dx:" + dx + "  dy:" + dy + "  cons:" + Arrays.toString(consumed) + "  offs:" + Arrays.toString(offsetInWindow));
        return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
    }

    /**
     * 在嵌套滑动的子View fling之后在调用该函数向父View汇报fling情况
     *
     * @param velocityX 水平方向速度
     * @param velocityY 垂直方向速度
     * @param consumed  true:子View fling了,false:子View没有 fling
     * @return true 父View fling了
     */
    @Override
    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
        Log.i(TAG,"分发Fling dispatchNestedFling  vX:" + velocityX + "  vY:" + velocityY + "   consumed:" + consumed);
        return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
    }

    /**
     * 在嵌套滑动的子View fling之前告诉父View fling情况
     *
     * @param velocityX 水平方向的速度
     * @param velocityY 垂直方向的速度
     * @return true: 父View fling了
     */
    @Override
    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
        Log.i(TAG,"准备分发Fling  dispatchNestedPreFling  vX:" + velocityX + "  vY:" + velocityY);
        return mChildHelper.dispatchNestedPreFling(velocityX, velocityY);
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mChildHelper.onDetachedFromWindow();
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        Log.e(TAG,"onScrollChanged");
    }
}

ChildView主要做了:

  • 在构造方法中开启嵌套滑动 setNestedScrollingEnabled(true);,内部调用mChildHelper.setNestedScrollingEnabled(enabled);
  • 在ACTION_DOWN 事件中调用startNestedScroll()通知父View开始嵌套滑动,内部调用mChildHelper.startNestedScroll(axes)
  • 调用dispatchNestedPreScroll()方法将滚动事件分发给父View,并接收父View消耗的距离,内部调用mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow)

NestedParentView

public class NestedParentView extends FrameLayout implements NestedScrollingParent {

    private static final String TAG = "zzy 父View";

    private NestedScrollingParentHelper mParentHelper;

    public NestedParentView(@NonNull Context context) {
        this(context,null);
    }

    public NestedParentView(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public NestedParentView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mParentHelper = new NestedScrollingParentHelper(this);
    }

    /**
     * 有嵌套滑动,询问该父View是否接受嵌套滑动
     *
     * @param child 直接子类(层级 child >= target)
     * @param target 发起嵌套滚动的视图
     * @param nestedScrollAxes
     * @return 是否接受嵌套滑动
     */
    @Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
        Log.d(TAG,"onStartNestedScroll 收到子View嵌套滑动,返回true,跟随滑动。 " +
                " child:" + child.getClass().getSimpleName() + "  target:" + target.getClass().getSimpleName() + "  axes: " + nestedScrollAxes);
        return true;
    }

    /**
     * 响应嵌套滚动的成功声明(接受嵌套滑动)
     * onStartNestedScroll返回true,该函数被调用
     */
    @Override
    public void onNestedScrollAccepted(View child, View target, int axes) {
        mParentHelper.onNestedScrollAccepted(child, target, axes);
        Log.d(TAG,"onNestedScrollAccepted  已经接受嵌套滑动");
    }

    /**
     * 停止嵌套滑动
     *
     * @param child 具体嵌套滑动的那个子类
     */
    @Override
    public void onStopNestedScroll(View child) {
        Log.d(TAG,"onStopNestedScroll");
        mParentHelper.onStopNestedScroll(child);
    }

    /**
     * 响应进行中的嵌套滚动(嵌套滑动的子View在滑动之后传递过来的滑动情况)
     *
     * @param target 嵌套滑动的子View
     * @param dxConsumed 水平方向target滑动的距离
     * @param dyConsumed 竖直方向target滑动的距离
     * @param dxUnconsumed 水平方向target未滑动的距离
     * @param dyUnconsumed 竖直方向target未滑动的距离
     */
    @Override
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        Log.d(TAG,"onNestedScroll");
    }

    /**
     * 在嵌套滑动的子View未滑动之前,通知准备(响应target)滑动的情况
     *
     * @param target 嵌套滑动的View
     * @param dx 水平方向target想要移动的距离
     * @param dy 竖直方向target想要移动的距离
     * @param consumed 输出结果,告诉子View当前父View消耗的距离,让子View做出相应调整
     *                 consumed[0] 水平消耗的距离
     *                 consumed[1] 垂直消耗的距离。
     */
    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        final View child = target;
        Log.d(TAG,"onNestedPreScroll 收到: dx:" + dx + "  dy:" + dy + "  consumed: " + Arrays.toString(consumed));
        if (dx > 0){
            if (child.getRight() + dx > getWidth()){
                dx = child.getRight() + dx - getWidth(); //多出来的部分
                offsetLeftAndRight(dx);
                consumed[0] += dx; //父View消耗
            }
        }else {
            if (child.getLeft() + dx < 0){
                dx += child.getLeft();
                offsetLeftAndRight(dx);
                consumed[0] += dx; //父View消耗
            }
        }

        if (dy > 0){
            if (child.getBottom() + dy > getHeight()){
                dy = child.getBottom() + dy - getHeight();
                offsetTopAndBottom(dy);
                consumed[1] += dy;
            }
        }else {
            if (child.getTop() + dy < 0){
                dy = dy + child.getTop();
                offsetTopAndBottom(dy);
                consumed[1] += dy;
            }
        }
        Log.d(TAG,"onNestedPreScroll 输出: dx:" + dx + "  dy:" + dy + "  consumed: " + Arrays.toString(consumed));
    }

    /**
     * 嵌套滑动的子View在fling之后报告过来的fling情况
     *
     * @param target
     * @param velocityX
     * @param velocityY
     * @param consumed
     * @return 父View是否消耗了fling
     */
    @Override
    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
        Log.d(TAG,"onNestedFling");
        return false;
    }

    /**
     *
     * 在嵌套滑动的子View未fling之前告诉过来的准备fling的情况
     * @param target 嵌套滑动的View
     * @param velocityX
     * @param velocityY
     * @return 父View是否消耗fling
     */
    @Override
    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
        Log.d(TAG,"onNestedPreFling");
        return false;
    }

    /**
     * 获得滚动方向
     *
     * @return 返回滚动轴
     * @see ViewCompat#SCROLL_AXIS_HORIZONTAL
     * @see ViewCompat#SCROLL_AXIS_VERTICAL
     * @see ViewCompat#SCROLL_AXIS_NONE
     */
    @Override
    public int getNestedScrollAxes() {
        return super.getNestedScrollAxes();
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        Log.e(TAG,"onScrollChanged");
    }
}

我练习的时候就是用的这个例子,出处已经找不到了。

你可能感兴趣的:(UI)