安卓滚动之嵌套滚动NestedScrollxx

目录

  • 为什么需要滚动
  • 滚动功能描述
  • 调用流程图
  • 简单案例

为什么需要嵌套滚动

事件处理中,某个事件的处理都是由一个View完成,不能将该View的事件处理交给其他的View。NestedScrollxxx接口,提供了一种解决方案,可以事件View树协同处理某一个滚动事件。从而更为优雅的实现各种绚丽的滚动效果。

嵌套滚动,并不是嵌套滑动。滚动是布局内容的滚动(Scroll),而滚动式布局位置的变化(Move).核心接口为NestedScrollChild和NestedScrollParent

NestedScrollxxx功能描述

NestedScrollChild
public interface NestedScrollingChild {
    /**
     *使当前View实例能嵌套滚动
     */
    void setNestedScrollingEnabled(boolean enabled);

    /**
     * 当前View是否开起了嵌套滚动
     */
    boolean isNestedScrollingEnabled();

    /**
    *通常在onTouch事件的MotionEvent#ACTION_DOWN中调用该方法开始滚动。如果调用了ViewParent#requestDisallowInterceptTouchEvent(boolean)
    *方法,嵌套滚动自动结束。通常结束滚动的方法是调用{@link #stopNestedScroll()} 
     * 返回值为true时,表示有可以嵌套滚动的父类被找到,否则将忽略掉本次滚动。
     *(如果当前正处于滚动当中,该方法直接返回true)
     */
    boolean startNestedScroll(@ScrollAxis int axes);

    /**
     * 停止嵌套滚动,如果当然没有处于滚动,并调用该方法,将是不友好的
     */
    void stopNestedScroll();

    /**
     *
     *当前view是否有一个可嵌套滚动的父亲 
     */
    boolean hasNestedScrollingParent();

    /**
     * 嵌套滚动中的滚动信息的分发(先自己,后父亲)
     *
     * @param dxConsumed 当前View水平消耗的距离(单位px)
     * @param dyConsumed 当前View垂直消耗的距离(单位px)
     * @param dxUnconsumed 当前View水平未消耗的距离(单位px)
     * @param dyUnconsumed 当前View垂直未消耗的距离(单位px)
     * @param offsetInWindow 可选参数.
     */
    boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
            int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow);

    /**
     * 嵌套滚动中的滚动信息的分发(先父亲,后自己)
     *
     * @param dx 水平需要滚动的距离(单位px)
     * @param dy 垂直需要滚动的距离(单位px)
     * @param consumed Output. consumed[0] 表示父亲(祖上)消耗的水平距离,
     consumed[1] 表示父亲(祖上)消耗的垂直距离,
     * @param offsetInWindow 可选参数
     */
    boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,
            @Nullable int[] offsetInWindow);

    /**
     * 将Fling分发给父亲
     * Fling的意思是:触摸滚动结束时有一个速度,而且这个速度的值大于
     *{@link ViewConfiguration#getScaledMinimumFlingVelocity() minimum fling velocity}
     * 场景:1 如果当前View滚动到了边界,可以调用该方法让其父亲消耗该Fling
     * 2 父亲有监听当前View滚动的需求
     * @param velocityX 每秒水平方向滚动的距离
     * @param velocityY 每秒垂直方向滚动的距离
     * @param consumed 当前View是否消耗该fling了
     * @return 父亲是否消耗处理了该FLing
     */
    boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);

    /**
     * 在自己处理fling之前,先传递给父亲去处理
     * @param velocityX 每秒水平方向滚动的距离
     * @param velocityY 每秒垂直方向滚动的距离
     * @param consumed 当前View是否消耗该fling了
     * @return 父亲是否消耗处理了该FLing
     */
    boolean dispatchNestedPreFling(float velocityX, float velocityY);
}
NestedScrollParent
public interface NestedScrollingParent {
    /**
     *对子view的startNestedScroll()方法的响应
     * @param child 直接子Viewe
     * @param target 初始化嵌套滚动的Viewe
     * @param axes 嵌套滚动的方向 {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},
     *                         {@link ViewCompat#SCROLL_AXIS_VERTICAL} or both
     * @return true 如果接受嵌套滚动,则返回true
     */
    boolean onStartNestedScroll(@NonNull View child, @NonNull View target, @ScrollAxis int axes);

    /**
     * 在onStartNestedScroll()或者onStartNestedScroll()方法调用后,被调用。目的就是在初始化view的嵌套配置
     * @param child 当前ViewGroup的直接子类
     * @param target 当前ViewGroup中初始化嵌套滚动的子孙
     * @param axes 滚动的方向 {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},
     *                         {@link ViewCompat#SCROLL_AXIS_VERTICAL} or both
     */
    void onNestedScrollAccepted(@NonNull View child, @NonNull View target, @ScrollAxis int axes);

    /**
     * 停止嵌套滚动后的 善后处理操作
     */
    void onStopNestedScroll(@NonNull View target);

    /**
     * 处理嵌套滚动
     * 前提是onStartNestedScroll()返回值为True
     * @param target 嵌套滚动发起的子孙View
     * @param dxConsumed 子孙View已经消费的水平距离
     * @param dyConsumed 子孙View已经消费的垂直距离
     * @param dxUnconsumed 子孙View未消费的水平距离
     * @param dyUnconsumed 子孙View未消费的垂直距离
     */
    void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
            int dxUnconsumed, int dyUnconsumed);

    /**
     * 在子View之前处理嵌套滚动
     *
     * @param target 嵌套滚动发起的子孙View
     * @param dx 事件触发时,水平需要滚动的距离
     * @param dy 事件触发时,垂直需要滚动的距离
     * @param consumed Output. 水平和垂直方向被父类消费的距离
     */
    void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed);

    /**
     * 当前ViewGroup实现嵌套Fling效果
     *
     * @param target 当前ViewGroup的中初始化嵌套Fling的子孙
     * @param velocityX 水平方向的速率
     * @param velocityY 垂直方向的速率
     * @param consumed targetView是否消耗过Fling了
     * @return true if this parent consumed or otherwise reacted to the fling
     */
    boolean onNestedFling(@NonNull View target, float velocityX, float velocityY, boolean consumed);

    /**
     * 当前ViewGroup优先消费嵌套Fling
     *
     * @param target 当前ViewGroup的中初始化嵌套Fling的子孙
     * @param velocityX 水平方向的速率
     * @param velocityY 垂直方向的速率
     * @return true 当前是否消费了嵌套fling
     */
    boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY);

    /**
     * 返回嵌套滚动的方向
     *
     */
    @ScrollAxis
    int getNestedScrollAxes();
}

时序图

lgdM28.png

简单案例

/**
 * ChildView
 *
 * @author zfc
 * @date 2020-01-07
 */
public class ChildView extends LinearLayout implements NestedScrollingChild {
    
    //速度跟踪器
    private VelocityTracker mVelocityTracker;
    //动画
    private Animation mAnimation;

    public ChildView(Context context) {
        super(context);
        init();
    }

    public ChildView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public ChildView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init(){
        mVelocityTracker = VelocityTracker.obtain();
        //使能嵌套滚动
        setNestedScrollingEnabled(true);
    }



    int left;
    int top;
    int right;
    int bottom;
    int startX;
    int startY;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                //
                startNestedScroll(SCROLL_AXIS_VERTICAL);

                left = getLeft();
                top = getTop();
                right = getRight();
                bottom = getBottom();
                startX = Math.round(event.getRawX());
                startY = Math.round(event.getRawY());
                break;
            case MotionEvent.ACTION_MOVE:

                int moveX = Math.round(event.getRawX());
                int moveY = Math.round(event.getRawY());
                int dx = 0;
                int dy = moveY-startY;

                int[] consumed = new int[2];
                int[] offsetInWindow = new int[2];
                //判断父亲是否消耗了该滚动距离
                if(dispatchNestedPreScroll(dx,dy,consumed,offsetInWindow)){
                    //暂时只讨论Y方向
                    int parentConsumedY = consumed[1];
                    left = left+dx;
                    top = top +parentConsumedY;
                    right = right+dx;
                    bottom = bottom+parentConsumedY;

                    //平滑滚动
                    scrollBy(0,(int)dy);
                }

                startX = moveX;
                startY = moveY;
                break;
            case MotionEvent.ACTION_UP:

                mVelocityTracker.computeCurrentVelocity(1000);
                float vx = mVelocityTracker.getXVelocity();
                float vy = mVelocityTracker.getYVelocity();
                moveX = Math.round(event.getRawX());
                moveY = Math.round(event.getRawY());
                fling(moveX,moveY,vx,vy);
                break;
            default:
                break;
        }
        return true;
    }

    private void fling(int moveX, int moveY, float vx, final float vy) {
        if (mAnimation == null) {
            mAnimation = new Animation() {
                @Override
                protected void applyTransformation(float interpolatedTime, Transformation t) {
                    float speed = mVelocityTracker.getXVelocity();
                    //interpolatedTime是当前的时间插值, 从0-1减速变化
                    //所以(1 - interpolatedTime)就是从1-0减速变化,
                    //而(1 - interpolatedTime) * speed就是将当前速度乘以插值,速度也会跟着从speed-0减速变化,
                    //将(1 - interpolatedTime) * speed)用于重绘,就可以实现平滑的滚动
                    scrollBy(0,(int)((1 - interpolatedTime) * vy));
                    Log.d("sliding", "cur speed = " + String.valueOf((1 - interpolatedTime) * speed));
                }
            };
            mAnimation.setInterpolator(new DecelerateInterpolator());//设置一个减速插值器
        }

        stopScroll();
        mAnimation.setDuration(2000);
        startAnimation(mAnimation);
    }


    /**
     * 停止滑动
     */
    private void stopScroll() {
        if (mAnimation != null && !mAnimation.hasEnded()) {
            mAnimation.cancel();
            clearAnimation();
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (mVelocityTracker != null) {//要记得回收
            mVelocityTracker.clear();
            mVelocityTracker.recycle();
            mVelocityTracker = null;
        }
    }

}


/**
 * ParentView
 *
 * @author zfc
 * @date 2020-01-07
 */
public class ParentView extends LinearLayout implements NestedScrollingParent {

    public ParentView(Context context) {
        super(context);
    }

    public ParentView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public ParentView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public ParentView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }


    //开启嵌套滚动
    @Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
        return true;
    }

    //做一些嵌套前的滚动配置
    @Override
    public void onNestedScrollAccepted(View child, View target, int axes) {
        super.onNestedScrollAccepted(child, target, axes);
    }

    //是否消耗滚动距离
    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        consumed[0] = 0;
        consumed[1] = dy/2;
        scrollBy(0,dy/2);
    }


}
发布了98 篇原创文章 · 获赞 6 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/dirksmaller/article/details/103888072