添加購物車控件(增加或減少數字)有動畫效果

最近在做一個關於商城的項目,有一個添加購物車的功能,我們的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>
有需要的同學可以去試試看,很好的效果


如果大家覺得我寫的還可以的話請關注我的微信公衆號:


會定時給大家推送技術點

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章