通过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 SpringBackLayout extends LinearLayout implements NestedScrollingParent {

    /**
     * 回弹View的背景颜色
     */
    private static final int COLOR_SPRING_BACK_VIEW_BG = 0xFF000000;
    /**
     * 动画时长
     */
    private static final int ANIM_DURATION = 260;
    /**
     * 头View
     */
    private final View mHeaderView;
    /**
     * 尾View
     */
    private final View mFooterView;
    private static final int MAX_WIDTH = 300;
    /**
     * 子View
     */
    private View mChildView;
    /**
     * 解决多点触控问题
     */
    private boolean isRunAnim;

    public SpringBackLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        setOrientation(LinearLayout.HORIZONTAL);
        mHeaderView = new View(context);
        mHeaderView.setBackgroundColor(COLOR_SPRING_BACK_VIEW_BG);
        mFooterView = new View(context);
        mFooterView.setBackgroundColor(COLOR_SPRING_BACK_VIEW_BG);
    }

    @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(@NonNull View child, @NonNull View target, int axes) {
    }

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

    /**
     * 复位初始位置
     */
    @Override
    public void onStopNestedScroll(@NonNull 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 = startProgress + ((endProgress - startProgress) * interpolatedTime);
            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(ANIM_DURATION);
            setInterpolator(new AccelerateInterpolator());
        }
    }

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

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

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

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

    @Override
    public boolean onNestedPreFling(@NonNull 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进行分发,最后在手指离开界面开启还原动画。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值