添加购物车控件(增加或减少数字)有动画效果

最近在做一个关于商城的项目,有一个添加购物车的功能,我们的UI给出来的东西是很好的,效果是很好的,但是完全不考虑我们程序员好不好容易实现,不过在坚持努力下,还是完成了:下面先来看一下效果图片:


话不多说了  下面来看一下实现方式吧:

public class RxShoppingView extends View {

    private final static int STATE_NONE = 0;
    private final static int STATE_MOVE = 1;
    private final static int STATE_MOVE_OVER = 2;
    private final static int STATE_ROTATE = 3;
    private final static int STATE_ROTATE_OVER = 4;

    private final static int DEFAULT_DURATION = 250;
    private final static String DEFAULT_SHOPPING_TEXT = "加入购物车";

    private Paint mPaintBg, mPaintText, mPaintNum;
    private Paint mPaintMinus;

    //是否是向前状态(= = 名字不好取,意思就是区分向前和回退状态)
    private boolean mIsForward = true;
    //动画时长
    private int mDuration;
    //购买数量
    private int mNum = 0;
    //展示文案
    private String mShoppingText;
    //当前状态
    private int mState = STATE_NONE;

    //属性值
    private int mWidth = 0;
    private int mAngle = 0;
    private int mTextPosition = 0;
    private int mMinusBtnPosition = 0;
    private int mAlpha = 0;

    private int MAX_WIDTH;
    private int MAX_HEIGHT;

    private ShoppingClickListener mShoppingClickListener;

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

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

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

    private void init(AttributeSet attrs) {

        TypedArray typeArray = getContext().obtainStyledAttributes(attrs,
                R.styleable.ShoppingView);
        mDuration = typeArray.getInt(R.styleable.ShoppingView_sv_duration, DEFAULT_DURATION);
        mShoppingText = TextUtils.isEmpty(typeArray.getString(R.styleable.ShoppingView_sv_text)) ? DEFAULT_SHOPPING_TEXT : typeArray.getString(R.styleable.ShoppingView_sv_text);
        //展示文案大小
        int textSize = (int) typeArray.getDimension(R.styleable.ShoppingView_sv_text_size, sp2px(16));
        //背景色
        int bgColor = typeArray.getColor(R.styleable.ShoppingView_sv_bg_color, ContextCompat.getColor(getContext(), R.color.slateblue));
        typeArray.recycle();

        mPaintBg = new Paint();
        mPaintBg.setColor(bgColor);
        mPaintBg.setStyle(Paint.Style.FILL);
        mPaintBg.setAntiAlias(true);
        mPaintMinus = new Paint();
        mPaintMinus.setColor(bgColor);
        mPaintMinus.setStyle(Paint.Style.STROKE);
        mPaintMinus.setAntiAlias(true);
        mPaintMinus.setStrokeWidth(textSize / 6);
        mPaintText = new Paint();
        mPaintText.setColor(Color.WHITE);
        mPaintText.setStrokeWidth(textSize / 6);
        mPaintText.setTextSize(textSize);
        mPaintText.setAntiAlias(true);
        mPaintNum = new Paint();
        mPaintNum.setColor(Color.BLACK);
        mPaintNum.setTextSize(textSize / 3 * 4);
        mPaintNum.setStrokeWidth(textSize / 6);
        mPaintNum.setAntiAlias(true);

        MAX_WIDTH = getTextWidth(mPaintText, mShoppingText) / 5 * 8;
        MAX_HEIGHT = textSize * 2;

        if (MAX_WIDTH / (float) MAX_HEIGHT < 3.5) {
            MAX_WIDTH = (int) (MAX_HEIGHT * 3.5);
        }

        mTextPosition = MAX_WIDTH / 2;
        mMinusBtnPosition = MAX_HEIGHT / 2;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(MAX_WIDTH, MAX_HEIGHT);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (mState == STATE_NONE) {
            drawBgMove(canvas);
            drawShoppingText(canvas);
        } else if (mState == STATE_MOVE) {
            drawBgMove(canvas);
        } else if (mState == STATE_MOVE_OVER) {
            mState = STATE_ROTATE;
            if (mIsForward) {
                drawAddBtn(canvas);
                startRotateAnim();
            } else {
                drawBgMove(canvas);
                drawShoppingText(canvas);
                mState = STATE_NONE;
                mIsForward = true;
                mNum = 0;
            }
        } else if (mState == STATE_ROTATE) {
            mPaintMinus.setAlpha(mAlpha);
            mPaintNum.setAlpha(mAlpha);
            drawMinusBtn(canvas, mAngle);
            drawNumText(canvas);
            drawAddBtn(canvas);
        } else if (mState == STATE_ROTATE_OVER) {
            drawMinusBtn(canvas, mAngle);
            drawNumText(canvas);
            drawAddBtn(canvas);
            if (!mIsForward) {
                startMoveAnim();
            }
        }

    }

    /**
     * 绘制移动的背景
     *
     * @param canvas 画板
     */
    private void drawBgMove(Canvas canvas) {
        canvas.drawArc(new RectF(mWidth, 0, mWidth + MAX_HEIGHT, MAX_HEIGHT), 90, 180, false, mPaintBg);
        canvas.drawRect(new RectF(mWidth + MAX_HEIGHT / 2, 0, MAX_WIDTH - MAX_HEIGHT / 2, MAX_HEIGHT), mPaintBg);
        canvas.drawArc(new RectF(MAX_WIDTH - MAX_HEIGHT, 0, MAX_WIDTH, MAX_HEIGHT), 180, 270, false, mPaintBg);
    }

    /**
     * 绘制购物车文案
     *
     * @param canvas 画板
     */
    private void drawShoppingText(Canvas canvas) {
        canvas.drawText(mShoppingText, MAX_WIDTH / 2 - getTextWidth(mPaintText, mShoppingText) / 2f, MAX_HEIGHT / 2 + getTextHeight(mShoppingText, mPaintText) / 2f, mPaintText);
    }

    /**
     * 绘制加号按钮
     *
     * @param canvas 画板
     */
    private void drawAddBtn(Canvas canvas) {
        canvas.drawCircle(MAX_WIDTH - MAX_HEIGHT / 2, MAX_HEIGHT / 2, MAX_HEIGHT / 2, mPaintBg);
        canvas.drawLine(MAX_WIDTH - MAX_HEIGHT / 2, MAX_HEIGHT / 4, MAX_WIDTH - MAX_HEIGHT / 2, MAX_HEIGHT / 4 * 3, mPaintText);
        canvas.drawLine(MAX_WIDTH - MAX_HEIGHT / 2 - MAX_HEIGHT / 4, MAX_HEIGHT / 2, MAX_WIDTH - MAX_HEIGHT / 4, MAX_HEIGHT / 2, mPaintText);
    }

    /**
     * 绘制减号按钮
     *
     * @param canvas 画板
     * @param angle  旋转角度
     */
    private void drawMinusBtn(Canvas canvas, float angle) {
        if (angle != 0) {
            canvas.rotate(angle, mMinusBtnPosition, MAX_HEIGHT / 2);
        }
        canvas.drawCircle(mMinusBtnPosition, MAX_HEIGHT / 2, MAX_HEIGHT / 2 - MAX_HEIGHT / 20, mPaintMinus);
        canvas.drawLine(mMinusBtnPosition - MAX_HEIGHT / 4, MAX_HEIGHT / 2, mMinusBtnPosition + MAX_HEIGHT / 4, MAX_HEIGHT / 2, mPaintMinus);
        if (angle != 0) {
            canvas.rotate(-angle, mMinusBtnPosition, MAX_HEIGHT / 2);
        }
    }

    /**
     * 绘制购买数量
     *
     * @param canvas 画板
     */
    private void drawNumText(Canvas canvas) {
        drawText(canvas, String.valueOf(mNum), mTextPosition - getTextWidth(mPaintNum, String.valueOf(mNum)) / 2f, MAX_HEIGHT / 2 + getTextHeight(String.valueOf(mNum), mPaintNum) / 2f, mPaintNum, mAngle);
    }

    /**
     * 绘制Text带角度
     *
     * @param canvas 画板
     * @param text   文案
     * @param x      x坐标
     * @param y      y坐标
     * @param paint  画笔
     * @param angle  旋转角度
     */
    private void drawText(Canvas canvas, String text, float x, float y, Paint paint, float angle) {
        if (angle != 0) {
            canvas.rotate(angle, x, y);
        }
        canvas.drawText(text, x, y, paint);
        if (angle != 0) {
            canvas.rotate(-angle, x, y);
        }
    }

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

                if (mState == STATE_NONE) {
                    mNum++;
                    startMoveAnim();
                    if (mShoppingClickListener != null) {
                        mShoppingClickListener.onAddClick(mNum);
                    }
                } else if (mState == STATE_ROTATE_OVER) {
                    if (isPointInCircle(new PointF(event.getX(), event.getY()), new PointF(MAX_WIDTH - MAX_HEIGHT / 2, MAX_HEIGHT / 2), MAX_HEIGHT / 2)) {
                        if (mNum > 0) {
                            mNum++;
                            mIsForward = true;
                            if (mShoppingClickListener != null) {
                                mShoppingClickListener.onAddClick(mNum);
                            }
                        }
                        invalidate();
                    } else if (isPointInCircle(new PointF(event.getX(), event.getY()), new PointF(MAX_HEIGHT / 2, MAX_HEIGHT / 2), MAX_HEIGHT / 2)) {
                        if (mNum > 1) {
                            mNum--;
                            if (mShoppingClickListener != null) {
                                mShoppingClickListener.onMinusClick(mNum);
                            }
                            invalidate();
                        } else {
                            if (mShoppingClickListener != null) {
                                mShoppingClickListener.onMinusClick(0);
                            }
                            mState = STATE_ROTATE;
                            mIsForward = false;
                            startRotateAnim();
                        }
                    }
                }

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

    /**
     * 开始移动动画
     */
    private void startMoveAnim() {
        mState = STATE_MOVE;
        ValueAnimator valueAnimator;
        if (mIsForward) {
            valueAnimator = ValueAnimator.ofInt(0, MAX_WIDTH - MAX_HEIGHT);
        } else {
            valueAnimator = ValueAnimator.ofInt(MAX_WIDTH - MAX_HEIGHT, 0);
        }
        valueAnimator.setDuration(mDuration);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {

                mWidth = (Integer) valueAnimator.getAnimatedValue();

                if (mIsForward) {
                    if (mWidth == MAX_WIDTH - MAX_HEIGHT) {
                        mState = STATE_MOVE_OVER;
                    }
                } else {
                    if (mWidth == 0) {
                        mState = STATE_MOVE_OVER;
                    }
                }

                invalidate();
            }
        });
        valueAnimator.start();
    }

    /**
     * 开始旋转动画
     */
    private void startRotateAnim() {

        Collection<Animator> animatorList = new ArrayList<>();

        ValueAnimator animatorTextRotate;
        if (mIsForward) {
            animatorTextRotate = ValueAnimator.ofInt(0, 360);
        } else {
            animatorTextRotate = ValueAnimator.ofInt(360, 0);
        }
        animatorTextRotate.setDuration(mDuration);
        animatorTextRotate.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {

                mAngle = (Integer) valueAnimator.getAnimatedValue();

                if (mIsForward) {
                    if (mAngle == 360) {
                        mState = STATE_ROTATE_OVER;
                    }
                } else {
                    if (mAngle == 0) {
                        mState = STATE_ROTATE_OVER;
                    }
                }

            }
        });

        animatorList.add(animatorTextRotate);

        ValueAnimator animatorAlpha;
        if (mIsForward) {
            animatorAlpha = ValueAnimator.ofInt(0, 255);
        } else {
            animatorAlpha = ValueAnimator.ofInt(255, 0);
        }
        animatorAlpha.setDuration(mDuration);
        animatorAlpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {

                mAlpha = (Integer) valueAnimator.getAnimatedValue();

                if (mIsForward) {
                    if (mAlpha == 255) {
                        mState = STATE_ROTATE_OVER;
                    }
                } else {
                    if (mAlpha == 0) {
                        mState = STATE_ROTATE_OVER;
                    }
                }

            }
        });

        animatorList.add(animatorAlpha);

        ValueAnimator animatorTextMove;
        if (mIsForward) {
            animatorTextMove = ValueAnimator.ofInt(MAX_WIDTH - MAX_HEIGHT / 2, MAX_WIDTH / 2);
        } else {
            animatorTextMove = ValueAnimator.ofInt(MAX_WIDTH / 2, MAX_WIDTH - MAX_HEIGHT / 2);
        }
        animatorTextMove.setDuration(mDuration);
        animatorTextMove.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {

                mTextPosition = (Integer) valueAnimator.getAnimatedValue();

                if (mIsForward) {
                    if (mTextPosition == MAX_WIDTH / 2) {
                        mState = STATE_ROTATE_OVER;
                    }
                } else {
                    if (mTextPosition == MAX_WIDTH - MAX_HEIGHT / 2) {
                        mState = STATE_ROTATE_OVER;
                    }
                }

            }
        });

        animatorList.add(animatorTextMove);

        ValueAnimator animatorBtnMove;
        if (mIsForward) {
            animatorBtnMove = ValueAnimator.ofInt(MAX_WIDTH - MAX_HEIGHT / 2, MAX_HEIGHT / 2);
        } else {
            animatorBtnMove = ValueAnimator.ofInt(MAX_HEIGHT / 2, MAX_WIDTH - MAX_HEIGHT / 2);
        }
        animatorBtnMove.setDuration(mDuration);
        animatorBtnMove.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {

                mMinusBtnPosition = (Integer) valueAnimator.getAnimatedValue();

                if (mIsForward) {
                    if (mMinusBtnPosition == MAX_HEIGHT / 2) {
                        mState = STATE_ROTATE_OVER;
                    }
                } else {
                    if (mMinusBtnPosition == MAX_WIDTH - MAX_HEIGHT / 2) {
                        mState = STATE_ROTATE_OVER;
                    }
                }

                invalidate();
            }
        });

        animatorList.add(animatorBtnMove);

        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.setDuration(mDuration);
        animatorSet.playTogether(animatorList);
        animatorSet.start();
    }

    /**
     * 设置购买数量
     *
     * @param num 购买数量
     */
    public void setTextNum(int num) {
        mNum = num;
        mState = STATE_ROTATE_OVER;
        invalidate();
    }

    public void setOnShoppingClickListener(ShoppingClickListener shoppingClickListener) {
        this.mShoppingClickListener = shoppingClickListener;
    }

    public interface ShoppingClickListener {
        void onAddClick(int num);

        void onMinusClick(int num);
    }

    /**
     * 判断点是否在圆内
     *
     * @param pointF 待确定点
     * @param circle 圆心
     * @param radius 半径
     * @return true在圆内
     */
    private boolean isPointInCircle(PointF pointF, PointF circle, float radius) {
        return Math.pow((pointF.x - circle.x), 2) + Math.pow((pointF.y - circle.y), 2) <= Math.pow(radius, 2);
    }

    private int sp2px(float spValue) {
        final float fontScale = getContext().getResources().getDisplayMetrics().scaledDensity;
        return (int) (spValue * fontScale + 0.5f);
    }

    //获取Text高度
    private int getTextHeight(String str, Paint paint) {
        Rect rect = new Rect();
        paint.getTextBounds(str, 0, str.length(), rect);
        return (int) (rect.height() / 33f * 29);
    }

    //获取Text宽度
    private int getTextWidth(Paint paint, String str) {
        int iRet = 0;
        if (str != null && str.length() > 0) {
            int len = str.length();
            float[] widths = new float[len];
            paint.getTextWidths(str, widths);
            for (int j = 0; j < len; j++) {
                iRet += (int) Math.ceil(widths[j]);
            }
        }
        return iRet;
    }

}
里面用到了自定义的属性所以需要创建attrs.xml

然后在里面定义

<declare-styleable name="ShoppingView">
    <attr name="sv_bg_color" format="color"/>
    <attr name="sv_text" format="string"/>
    <attr name="sv_text_size" format="dimension"/>
    <attr name="sv_duration" format="integer"/>
</declare-styleable>
有需要的同学可以去试试看,很好的效果


如果大家觉得我写的还可以的话请关注我的微信公众号:


会定时给大家推送技术点

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值