通过NestedScrolling实现RecyclerView拖拽回弹效果

简介:

  • Android5.0后Google为Android的滑动机制提供了NestedScrolling特性,可以使我们对嵌套View更简单事件拦截处理。本篇就对NestedScrolling实际应用做一些讲解。


NestedScrolling与传统事件分发机制作对比:
  • 比如某个外部的ViewGroup拦截掉内部View的事件,那本次事件会被ViewGroup消费并且不会向下传递,如果子view也想处理只能等待下一次手指再按下。
  • NestedScrolling可以很好的解决传统事件拦截的缺点,内部View在滚动的时候通过NestedScrollingChild将dx,dy交给NestedScrollingParent,NestedScrollingParent可对其进行部分消耗,剩余的部分还给内部View。

而NestedScrolling也是support.v4提供的支持类,在老版本也可以很好的兼容。


描述:

通过NestedScrolling可以实现哪些效果?
  • 菜单悬停效果
  • 下拉刷新、上拉加载更多
  • 拖拽回弹
实现嵌套分发主要通过以下两个接口:

NestedScrollingParent
NestedScrollingChild

要使用 NestedScrolling机制,父View 需要实现NestedScrollingParent接口,而子View需要实现NestedScrollingChild接口。RecyclerView已经默认实现了NestedScrollingChild,如果RecyclerView不满足你的业务需求,那需要去实现NestedScrollingChild,这里我只对NestedScrollingParent实现。


实现:

public class DZStickyNavLayouts extends LinearLayout implements NestedScrollingParent {

    private View mHeaderView;
    private View mFooterView;
    private static final int MAX_WIDTH = 500;
    private View mChildView;
    // 解决多点触控问题
    private boolean isRunAnim;
    private int mDrag = 2;

    public DZStickyNavLayouts(Context context, AttributeSet attrs) {
        super(context, attrs);
        setOrientation(LinearLayout.HORIZONTAL);
        mHeaderView = new View(context);
        mHeaderView.setBackgroundColor(0xffFFC125);
        mFooterView = new View(context);
        mFooterView.setBackgroundColor(0xffFF3E96);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mChildView = getChildAt(0);
        LayoutParams layoutParams = new LayoutParams(MAX_WIDTH, LayoutParams.MATCH_PARENT);
        addView(mHeaderView, 0, layoutParams);
        addView(mFooterView, getChildCount(), layoutParams);
        // 左移
        scrollBy(MAX_WIDTH, 0);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        ViewGroup.LayoutParams params = mChildView.getLayoutParams();
        params.width = getMeasuredWidth();
    }

    /**
     * 必须要复写 onStartNestedScroll后调用
     */
    @Override
    public void onNestedScrollAccepted(View child, View target, int axes) {

    }

    /**
     * 返回true代表处理本次事件
     */
    @Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
        if (target instanceof RecyclerView && !isRunAnim) {
            return true;
        }
        return false;
    }

    /**
     * 复位初始位置
     */
    @Override
    public void onStopNestedScroll(View target) {
        startAnimation(new ProgressAnimation());
    }

    /**
     * 回弹动画
     */
    private class ProgressAnimation extends Animation {
        // 预留
        private float startProgress = 0;
        private float endProgress = 1;

        private ProgressAnimation(){
            isRunAnim = true;
        }

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            float progress = ((endProgress - startProgress) * interpolatedTime) + startProgress;
            scrollBy((int) ((MAX_WIDTH - getScrollX()) * progress), 0);
            if (progress == 1) {
                isRunAnim = false;
            }
        }

        @Override
        public void initialize(int width, int height, int parentWidth, int parentHeight) {
            super.initialize(width, height, parentWidth, parentHeight);
            setDuration(260);
            setInterpolator(new AccelerateInterpolator());
        }
    }

    @Override
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {

    }

    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        // 如果在自定义ViewGroup之上还有父View交给我来处理
        getParent().requestDisallowInterceptTouchEvent(true);
        // dx>0 往左滑动 dx<0往右滑动
        boolean hiddenLeft = dx > 0 && getScrollX() < MAX_WIDTH && !ViewCompat.canScrollHorizontally(target, -1);
        boolean showLeft = dx < 0 && !ViewCompat.canScrollHorizontally(target, -1);
        boolean hiddenRight = dx < 0 && getScrollX() > MAX_WIDTH && !ViewCompat.canScrollHorizontally(target, 1);
        boolean showRight = dx > 0 && !ViewCompat.canScrollHorizontally(target, 1);
        if (hiddenLeft || showLeft || hiddenRight || showRight) {
            scrollBy(dx / mDrag, 0);
            consumed[0] = dx;
        }

        // 限制错位问题
        if (dx > 0 && getScrollX() > MAX_WIDTH && !ViewCompat.canScrollHorizontally(target, -1)) {
            scrollTo(MAX_WIDTH, 0);
        }
        if (dx < 0 && getScrollX() < MAX_WIDTH && !ViewCompat.canScrollHorizontally(target, 1)) {
            scrollTo(MAX_WIDTH, 0);
        }
    }

    @Override
    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
        return false;
    }


    @Override
    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
        // 当RecyclerView在界面之内交给它自己惯性滑动
        if (getScrollX() == MAX_WIDTH) {
            return false;
        }
        return true;
    }

    @Override
    public int getNestedScrollAxes() {
        return 0;
    }

    /**
     * 限制滑动 移动x轴不能超出最大范围
     */
    @Override
    public void scrollTo(int x, int y) {
        if (x < 0) {
            x = 0;
        } else if (x > MAX_WIDTH * 2) {
            x = MAX_WIDTH * 2;
        }
        super.scrollTo(x, y);
    }
}

具体实现思路就是给RecyclerView添加头和尾,并控制滑动距离然后通过NestedScrolling进行分发,最后在手指离开界面开启还原动画。

文章参考:
http://blog.csdn.net/lmj623565791/article/details/52204039

你可能感兴趣的:(通过NestedScrolling实现RecyclerView拖拽回弹效果)