自定义RecyclerView左滑刷新

其实这个我是在github上面找到的主要是为了学习,而熟悉,以备后面用到,在此基础上进行修改实现其他刷新,

AnimView类主要是为了实现给Moreview添加背景色被实现滑动时候出现贝塞尔曲线效果

public class AnimView extends View {

    boolean isBezierBackDone = false;

    private int mWidth;
    private int mHeight;
    private int PULL_WIDTH;
    private int PULL_DELTA;

    private long mStart;
    private long mStop;
    private int mBezierDeta;

    private long bezierBackDur;

    private Paint mBackPaint;
    private Path mPath;

    private AnimatorStatus mAniStatus = AnimatorStatus.PULL_LEFT;

    enum AnimatorStatus{
        PULL_LEFT,
        DRAG_LEFT,
        RELEASE,
    }

    public AnimView(Context context) {
        this(context, null, 0);
    }

    public AnimView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public AnimView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context){
        PULL_WIDTH = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, context.getResources().getDisplayMetrics());
        PULL_DELTA = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 80, context.getResources().getDisplayMetrics());

        mPath = new Path();
        mBackPaint = new Paint();
        mBackPaint.setAntiAlias(true);
        mBackPaint.setStyle(Paint.Style.FILL);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = MeasureSpec.getSize(widthMeasureSpec);
        if (width > PULL_DELTA + PULL_WIDTH) {
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(PULL_DELTA + PULL_WIDTH, MeasureSpec.getMode(widthMeasureSpec));
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (changed) {
            mWidth = getWidth();
            mHeight = getHeight();

            if (mWidth < PULL_WIDTH) {
                mAniStatus = AnimatorStatus.PULL_LEFT;
            }


            switch (mAniStatus) {
                case PULL_LEFT:
                    if (mWidth >= PULL_WIDTH) {
                        mAniStatus = AnimatorStatus.DRAG_LEFT;
                    }
                    break;
            }

        }
    }



    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        switch (mAniStatus){
            case PULL_LEFT:
                canvas.drawRect(0, 0, mWidth, mHeight, mBackPaint);
                break;
            case DRAG_LEFT:
                drawDrag(canvas);
                break;
            case RELEASE:
                drawBack(canvas, getBezierDelta());
                break;
        }
    }

    private void drawDrag(Canvas canvas) {
        canvas.drawRect(mWidth - PULL_WIDTH, 0, mWidth, mHeight, mBackPaint);

        mPath.reset();
        mPath.moveTo(mWidth - PULL_WIDTH, 0);
        mPath.quadTo(0, mHeight / 2, mWidth - PULL_WIDTH, mHeight);
        canvas.drawPath(mPath, mBackPaint);
    }

    private void drawBack(Canvas canvas, int delta){
        mPath.reset();
        mPath.moveTo(mWidth, 0);
        mPath.lineTo(mWidth - PULL_WIDTH, 0);
        mPath.quadTo(delta, mHeight / 2,  mWidth - PULL_WIDTH, mHeight);
        mPath.lineTo(mWidth, mHeight);
        canvas.drawPath(mPath, mBackPaint);

        invalidate();

        if(bezierBackRatio == 1){
            isBezierBackDone = true;
        }

        if(isBezierBackDone && mWidth <= PULL_WIDTH){
            drawFooterBack(canvas);
        }
    }

    private void drawFooterBack(Canvas canvas){
        canvas.drawRect(0, 0, mWidth, mHeight, mBackPaint);
    }

    public void releaseDrag(){
        mAniStatus = AnimatorStatus.RELEASE;
        mStart = System.currentTimeMillis();
        mStop = mStart + bezierBackDur;
        mBezierDeta = mWidth - PULL_WIDTH;
        isBezierBackDone = false;
        requestLayout();
    }

    public void setBezierBackDur(long bezierBackDur) {
        this.bezierBackDur = bezierBackDur;
    }

    private float bezierBackRatio;

    private int getBezierDelta() {
        bezierBackRatio = getBezierBackRatio();
        return (int) (mBezierDeta * bezierBackRatio);
    }

    private float getBezierBackRatio() {
        if (System.currentTimeMillis() >= mStop) {
            return 1;
        }
        float ratio = (System.currentTimeMillis() - mStart) / (float) bezierBackDur;
        return Math.min(1, ratio);
    }

    public void setBgColor(int color){
        mBackPaint.setColor(color);
    }
}

PullLeftToRefreshLayout实现刷新

public class PullLeftToRefreshLayout extends FrameLayout {
    private static final Interpolator ANIMATION_INTERPOLATOR = new LinearInterpolator();
    private static final long BACK_ANIM_DUR = 500;//设置返回滑动松开childView返回的时间
    private static final long BEZIER_BACK_DUR = 350;//设置贝塞尔曲线的长度
    private static final int ROTATION_ANIM_DUR = 200;//设置箭头的旋转的时间
    private OnScrollListener mScrollListener;
    private OnRefreshListener mOnRefreshListener;

    /**
     * MoreView移动的最大距离
     */
    private static float MORE_VIEW_MOVE_DIMEN;
    private static final int ROTATION_ANGLE = 180;

    private static String SCAN_MORE;
    private static String RELEASE_SCAN_MORE;

    private float mTouchStartX;
    private float mTouchCurX;

    private float mPullWidth;
    private float mFooterWidth;

    /**
     * 目的是为了将moreView隐藏以便滑动
     */
    private int moreViewMarginRight;
    private int footerViewBgColor;

    private boolean isRefresh = false;
    private boolean scrollState = false;

    private View mChildView;
    private AnimView footerView;
    private View moreView;
    private TextView moreText;
    private ImageView arrowIv;

    private ValueAnimator mBackAnimator;
    private RotateAnimation mArrowRotateAnim;
    private RotateAnimation mArrowRotateBackAnim;
    private DecelerateInterpolator interpolator = new DecelerateInterpolator(10);

    public void setScrollListener(OnScrollListener scrollListener) {
        mScrollListener = scrollListener;
    }

    public void setOnRefreshListener(OnRefreshListener onRefreshListener) {
        mOnRefreshListener = onRefreshListener;
    }

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

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

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

    private void init(Context context,AttributeSet attrs) {
        mPullWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100, context.getResources().getDisplayMetrics());
        MORE_VIEW_MOVE_DIMEN = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 40, context.getResources().getDisplayMetrics());
        mFooterWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, context.getResources().getDisplayMetrics());
        //设置childView为-26dp隐藏它
        moreViewMarginRight = -getResources().getDimensionPixelSize(R.dimen.dp_26);
        SCAN_MORE = getResources().getString(R.string.scan_more);
        RELEASE_SCAN_MORE = getResources().getString(R.string.release_scan_more);
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PullLeftToRefreshLayout);
        footerViewBgColor = ta.getColor(R.styleable.PullLeftToRefreshLayout_footerBgColor, Color.rgb(243, 242, 242));
        ta.recycle();
        this.post(new Runnable() {
            @Override
            public void run() {
                //获取PullLeftToRefreshLayout中的第一个view有且只有一个
                mChildView = getChildAt(0);
                //设置MoreView背景色
                addFooterView();
                //添加Moreview
                addMoreView();
                //设置回弹view动画
                initBackAnim();
                //设置刷新箭头动画
                initRotateAnim();
            }
        });
    }

    //添加背景实现滑动时候显示贝塞尔曲线效果
    private void addFooterView() {
        //设置显示位置
        LayoutParams params = new LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT);
        params.gravity = Gravity.RIGHT;

        footerView = new AnimView(getContext());
        footerView.setLayoutParams(params);
        footerView.setBgColor(Color.RED);
        footerView.setBezierBackDur(BEZIER_BACK_DUR);//设置贝塞尔曲线的在终点位置
        addViewInternal(footerView);//添加到FrameLayout容器中
    }

    //添加加载更多的视图view
    private void addMoreView() {
        LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        params.gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL;
        params.setMargins(0, 0, moreViewMarginRight, 0);

        moreView = LayoutInflater.from(getContext()).inflate(R.layout.item_load_more, this, false);
        moreView.setLayoutParams(params);
        moreText = (TextView) moreView.findViewById(R.id.tvMoreText);
        arrowIv = (ImageView) moreView.findViewById(R.id.ivRefreshArrow);
        addViewInternal(moreView);
    }

    private void initBackAnim() {
        if (mChildView == null) {
            return;
        }

        //当手指放开的时候执行这个动画,使得展开的刷新view和childview做回弹效果,也就是恢复到原来位置
        mBackAnimator = ValueAnimator.ofFloat(mPullWidth, 0);
        mBackAnimator.addListener(new AnimListener());
        mBackAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float val = (float) animation.getAnimatedValue();
                if (val <= mFooterWidth) {
                    val = interpolator.getInterpolation(val / mFooterWidth) * val;
                    footerView.getLayoutParams().width = (int) val;
                    footerView.requestLayout();
                }

                if (mChildView != null) {
                    mChildView.setTranslationX(-val);
                }
                moveMoreView(val, true);
            }
        });
        mBackAnimator.setDuration(BACK_ANIM_DUR);
    }

    @Override
    public void addView(View child) {
        /**
         * 当前刷新控件包裹中的view只能只有一个,必须是能滚动的控件,
         * 因为需要用canScrollHorizontally来判断是否内部控件已经滚到边缘
         */
        if (getChildCount() >= 1) {
            throw new RuntimeException("you can only attach one child");
        }
        mChildView = child;
        super.addView(child);
    }

    ///设置
    private void initRotateAnim() {
        int pivotType = Animation.RELATIVE_TO_SELF;
        float pivotValue = 0.5f;
        //当是手指滑动到左边最大距离的时候箭头做旋转动画
        mArrowRotateAnim = new RotateAnimation(0, ROTATION_ANGLE, pivotType, pivotValue, pivotType, pivotValue);
        mArrowRotateAnim.setInterpolator(ANIMATION_INTERPOLATOR);
        mArrowRotateAnim.setDuration(ROTATION_ANIM_DUR);
        mArrowRotateAnim.setFillAfter(true);

        mArrowRotateBackAnim = new RotateAnimation(ROTATION_ANGLE, 0, pivotType, pivotValue, pivotType, pivotValue);
        mArrowRotateBackAnim.setInterpolator(ANIMATION_INTERPOLATOR);
        mArrowRotateBackAnim.setDuration(ROTATION_ANIM_DUR);
        mArrowRotateBackAnim.setFillAfter(true);

    }

    private void addViewInternal(@NonNull View child) {
        super.addView(child);
    }


    public interface OnScrollListener {
        void onScrollChange(boolean scroll);
    }

    public interface OnRefreshListener {
        void onRefresh();
    }


    //当内部控件滑动到边缘的时候调用该方法实现移动moreview使其跟随手指滑动显示出来
    private void moveMoreView(float offsetx, boolean release) {
        float dx = offsetx / 2;
        if (dx <= MORE_VIEW_MOVE_DIMEN) {
            moreView.setTranslationX(-dx);//主要关键代码
            //箭头移动
            if (!release && switchMoreText(SCAN_MORE)) {
                arrowIv.clearAnimation();
                arrowIv.startAnimation(mArrowRotateBackAnim);
            }
        } else {
            //箭头移动
            if (switchMoreText(RELEASE_SCAN_MORE)) {
                arrowIv.clearAnimation();
                arrowIv.startAnimation(mArrowRotateAnim);
            }
        }
    }


    //判断当前文字是哪个来执行箭头动画
    private boolean switchMoreText(String text) {
        if (text.equals(moreText.getText().toString())) {
            return false;
        }
        moreText.setText(text);
        return true;
    }

    //设置arrowIv(箭头图片)的图片动画监听,就是实现刷新
    private class AnimListener implements Animator.AnimatorListener {

        @Override
        public void onAnimationStart(Animator animator) {
        }

        @Override
        public void onAnimationEnd(Animator animator) {
            if (mOnRefreshListener != null && isRefresh) {
                mOnRefreshListener.onRefresh();
            }
            moreText.setText(SCAN_MORE);
            arrowIv.clearAnimation();
            isRefresh = false;
        }

        @Override
        public void onAnimationCancel(Animator animator) {
        }

        @Override
        public void onAnimationRepeat(Animator animator) {
        }
    }

    //设置滚动的监听
    private void setScrollState(boolean scrollState) {
        if (this.scrollState == scrollState) {
            return;
        }
        this.scrollState = scrollState;
        if (mScrollListener != null) {
            mScrollListener.onScrollChange(scrollState);
        }
    }


    //判断滑动到底部
    private boolean canScrollRight() {
        if (mChildView == null) {
            return false;
        }
        //第一个代表滑动的view,第二个参数代表方向,负的代表左,正的代表右
        return ViewCompat.canScrollHorizontally(mChildView, 1);
    }

    //这里的拦截主要是做滚动监听
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        //如果处于刷新状态就拦截,使其进入onTouchEvent里面
        if (isRefresh) {
            return true;
        }

        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mTouchStartX = ev.getX();
                mTouchCurX = mTouchStartX;
                setScrollState(false);
                break;
            case MotionEvent.ACTION_MOVE:
                float curX = ev.getX();
                float dx = curX - mTouchCurX;

                if (dx < -10 && !canScrollRight()) {
                    setScrollState(true);
                    return true;
                }
                break;
        }

        return super.onInterceptTouchEvent(ev);
    }

    //moreview能出现和实现刷新都是要这里面(关键代码)
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (isRefresh) {
            //还处于刷新状态吧这个时间向上传递,也就是传递给childView使其能滚动
            return super.onTouchEvent(event);
        }

        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                mTouchCurX = event.getX();
                float dx = mTouchStartX - mTouchCurX;
                dx = Math.min(mPullWidth * 2, dx);
                dx = Math.max(0, dx);

                //如果mChildView不存在或者dx<=0(说明是手指向右滑动)
                if (mChildView == null || dx <= 0) {
                    return true;
                }

                float unit = dx / 2;
                float offsetx = interpolator.getInterpolation(unit / mPullWidth) * unit;
                mChildView.setTranslationX(-offsetx);
                footerView.getLayoutParams().width = (int) offsetx;//随着手指滑动而逐渐展开这里是背景色
                footerView.requestLayout();//刷新当前view
                moveMoreView(offsetx, false);//这里moreview也开始逐渐展开
                return true;

            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if (mChildView == null) {
                    return true;
                }

                //手指松开就回弹效果也就是footerview和moreview回到最初位置,就是mBackAnimator动画里面最回弹的
                float childDx = Math.abs(mChildView.getTranslationX());
                if (childDx >= mFooterWidth) {
                    //滑动到最大值 开始执行动画而在当前动画的监听里面就可以可以根据isRefresh来判断是否执行刷新方法
                    mBackAnimator.setFloatValues(childDx, 0);
                    mBackAnimator.start();
                    footerView.releaseDrag();

                    //根据moreText.getText和RELEASE_SCAN_MORE(释放刷新)
                    if (reachReleasePoint()) {
                        //改变刷新状态
                        isRefresh = true;
                    }
                } else {
                    //如果没有滑动到最大值 不改变刷新状态,也要最回弹
                    mBackAnimator.setFloatValues(childDx, 0);
                    mBackAnimator.start();
                }
                //设置滚动监听为false说明没有滚动
                setScrollState(false);

                return true;
        }
        return super.onTouchEvent(event);
    }

    //拿到text 如果是:RELEASE_SCAN_MORE(释放刷新)其实也可以不要这个,加了也不会错
    private boolean reachReleasePoint() {
        return RELEASE_SCAN_MORE.equals(moreText.getText().toString());
    }





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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值