圆形额度条(类似于清理大师的那个动画)

年底了,各种失业潮,尤其是互联网裁员信息不断,多学习总是有用的。
做了一个金融产品经常用到的View。

主要涉及到的知识:

  • 三角函数贯穿始终,各种转化,画刻度和圆都会用到。
  • 一些小技巧(设置货币符号和金额的距离)
  • 动画的执行
  • View的绘制流程(重点)

先看图后上代码,绘制流程就不废话了,因为代码已经注释的很清晰了。

image.gif
public class ArcProgressView extends View {

    /**
     * 不足之处:
     * 1.文字和整体图像的大小牵连起来
     * 2.开口的大小动态做出来
     * 3.渐变的颜色默认是三个,如果用两个,怎么去除第三个默认颜色
     */

    private Paint mPaint;
    /**
     * 中心点的坐标
     */
    private int mX, mY;
    /**
     * 中间圆弧的画笔宽度
     */
    private float strokeWith = 30f;
    /**
     * 外圆弧的画笔宽度
     */
    private float outStrokeWith = 4f;
    /**
     * 内圆弧的画笔宽度
     */
    private float innerStrokeWith = 2f;
    /**
     * 中心圆的半径
     */
    float mR = 200f;
    float mROut = mR + mR * 0.1f;
    float mRInner = mR - mR * 0.1f;
    /**
     * 两边耳朵的长度
     */
    float bothEarLength = mR * 0.1f;

    /**
     * 大刻度线的个数
     */
    private int mMainCalibration = 8;
    /**
     * 小刻度线的个数
     */
    private int mSecondaryCalibration = 22;
    /**
     * 大刻度线长度
     */
    private int mMainCalibrationLength = 30;
    /**
     * 小刻度线长度
     */
    private int mSecondaryCalibrationLength = 8;

    /*
       从-15的地方开始画刻度
     */
    private double mStartAngle = -15;
    private double mSecondaryStartAngle = -15;

    /**
     * 开口大小
     */
    float openRadian = 150;
    /**
     * 画弧开始的角度位置
     */
    float startAngle = openRadian + (180 - openRadian) / 2;

    /**
     * 整个圆的大小
     */
    float arcAngle = 360;

    /**
     * 背景画弧扫过的角度
     */
    float middleBgSweepAngle = arcAngle - openRadian;


    float sweepAngle = 0;

    private Context mContext;

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

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

    public ArcProgressView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        TypedArray attributes = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ArcProgress, defStyleAttr, 0);
        initByAttributes(attributes);
        attributes.recycle();
        initPaint();


    }

    private final int defaultMiddleArcColorBg = Color.argb(255, 232, 242, 252);
    private final int defaultMiddleArcColorOne = Color.parseColor("#D9465E");
    private final int defaultMiddleArcColorTwo = Color.parseColor("#2DA9F8");
    private final int defaultMiddleArcColorThree = Color.argb(255, 108, 132, 191);
    private final int defaultInnerArcColor = Color.parseColor("#DEF1FD");
    private final int defaultOuterArcColor = Color.parseColor("#DEF1FD");
    private final int defaultAvailableLimitColor = Color.argb(255, 29, 86, 166);
    private final int defaultTotalLimitColor = Color.argb(255, 29, 86, 166);
    private final int defaultCurrencySymbolColor = Color.argb(255, 58, 164, 196);
    private final int defaultAvailableLimitTextColor = Color.argb(255, 58, 164, 196);
    private final int defaultTotalLimitTextColor = Color.argb(255, 201, 201, 201);
    private int[] middleArcColor;
    private int middleArcColorBg;
    private int middleArcColorOne;
    private int middleArcColorTwo;
    private int middleArcColorThree;
    private int availableLimitColor;
    private int availableLimitTextColor;
    private int totalLimitColor;
    private int totalLimitTextColor;
    private int currencySymbolColor;


    private int innerArcColor;
    private int outerArcColor;

    private float availableLimit;
    private float totalLimit;

    private String availableLimitAlias;
    private String totalLimitAlias;

    protected void initByAttributes(TypedArray attributes) {
        middleArcColorBg = attributes.getColor(R.styleable.ArcProgress_middle_Arc_Color_Bg, defaultMiddleArcColorBg);
        middleArcColorOne = attributes.getColor(R.styleable.ArcProgress_middle_Arc_Color_One, defaultMiddleArcColorOne);
        middleArcColorTwo = attributes.getColor(R.styleable.ArcProgress_middle_Arc_Color_Two, defaultMiddleArcColorTwo);
        middleArcColorThree = attributes.getColor(R.styleable.ArcProgress_middle_Arc_Color_Three, defaultMiddleArcColorThree);
        innerArcColor = attributes.getColor(R.styleable.ArcProgress_inner_Arc_Color, defaultInnerArcColor);
        outerArcColor = attributes.getColor(R.styleable.ArcProgress_outer_Arc_Color, defaultOuterArcColor);
        availableLimitColor = attributes.getColor(R.styleable.ArcProgress_available_Limit_Color, defaultAvailableLimitColor);
        totalLimitColor = attributes.getColor(R.styleable.ArcProgress_total_Limit_Color, defaultTotalLimitColor);
        availableLimitTextColor = attributes.getColor(R.styleable.ArcProgress_available_Limit_Text_Color, defaultAvailableLimitTextColor);
        totalLimitTextColor = attributes.getColor(R.styleable.ArcProgress_total_Limit_Text_Color, defaultTotalLimitTextColor);
        currencySymbolColor = attributes.getColor(R.styleable.ArcProgress_currency_Symbol_Color, defaultCurrencySymbolColor);

        if (!TextUtils.isEmpty(attributes.getString(R.styleable.ArcProgress_available_Limit_Alias))) {
            setAvailableLimitAlias(attributes.getString(R.styleable.ArcProgress_available_Limit_Alias));
        } else {
            setAvailableLimitAlias("Available Limit");
        }

        if (!TextUtils.isEmpty(attributes.getString(R.styleable.ArcProgress_total_Limit_Alias))) {
            setTotalLimitAlias(attributes.getString(R.styleable.ArcProgress_total_Limit_Alias));
        } else {
            setTotalLimitAlias("Total limit");
        }

        setAvailableLimit(attributes.getFloat(R.styleable.ArcProgress_available_Limit, 0));
        setTotalLimit(attributes.getFloat(R.styleable.ArcProgress_total_Limit, 0));
    }

    private void setTotalLimitAlias(String string) {
        this.totalLimitAlias = string;
        this.invalidate();
    }

    public String getTotalLimitAlias() {
        return totalLimitAlias;
    }

    public void setAvailableLimitAlias(String string) {
        this.availableLimitAlias = string;
        this.invalidate();
    }

    public String getAvailableLimitAlias() {
        return availableLimitAlias;
    }


    public void setTotalLimit(float totalLimit) {
        this.totalLimit = totalLimit;
        if (totalLimit > 0) {
            startAnimator();
            invalidate();
        }
    }

    public float getTotalLimit() {
        return totalLimit;
    }

    public void setAvailableLimit(float availableLimit) {
        this.availableLimit = availableLimit;
        if (availableLimit > 0) {
            startAnimator();
            invalidate();
        }
    }

    /**
     * 扫过的动画
     */
    private void startAnimator() {
        if (availableLimit > 0 && totalLimit > 0) {
            if (availableLimit > totalLimit) {
                availableLimit = totalLimit;
            }

            sweepAngle = ((arcAngle - openRadian) * availableLimit) / totalLimit;
            ValueAnimator.AnimatorUpdateListener animatorUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    currentSweep = (int) animation.getAnimatedValue();
                    invalidate();
                }
            };

            ValueAnimator valueAnimator = ValueAnimator.ofInt(0, (int) sweepAngle);
            valueAnimator.addUpdateListener(animatorUpdateListener);
            valueAnimator.setDuration(500);
            valueAnimator.setInterpolator(new DecelerateInterpolator(0.6f));
            valueAnimator.setRepeatMode(ValueAnimator.RESTART);
            valueAnimator.start();
        }
    }


    public float getAvailableLimit() {
        return availableLimit;
    }


    @Override
    public void invalidate() {
        initPaint();
        super.invalidate();
    }

    private void initPaint() {
        middleArcColor = new int[]{defaultMiddleArcColorOne, defaultMiddleArcColorTwo, defaultMiddleArcColorThree};
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(strokeWith);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //获取所在父控件或者 的位置
        mX = w / 2;
        mY = h / 2;
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
    }


    float currentSweep = 0.0f;


    @SuppressLint("DrawAllocation")
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //画三个圆弧
        drawArcMiddleBackground(canvas);

        drawArcMiddle(canvas, currentSweep);
//        drawArcMiddle(canvas, sweepAngle);
        drawArcOut(canvas);
        drawArcInner(canvas);
        //画主刻度
        drawMainCalibration(canvas);

        //画分刻度
        drawSecondaryCalibration(canvas);
        //画可用额度的文字
        drawAvailableLimitAlias(canvas, availableLimitAlias);
        DecimalFormat decimalFormat = new DecimalFormat(",###");
        //画可用额度的数字大小
        drawTextAvailableLimit(canvas, decimalFormat.format(availableLimit));
        //画总额度的text
        drawTotalLimitAlias(canvas, totalLimitAlias);
        //画总额度的数字大小
        drawTextTotalLimit(canvas, decimalFormat.format(totalLimit));
    }


    public float getArcAngle() {
        return currentSweep;
    }

    public void setArcAngle(float arcAngle) {
        this.currentSweep = arcAngle;
    }

    /**
     * 画可用额度
     *
     * @param canvas
     * @param sweepAngle
     */
    private void drawArcMiddle(Canvas canvas, float sweepAngle) {
        LinearGradient linearGradient = new LinearGradient(
                (float) (mX - Math.sqrt((mR * mR - (mR / 2) * (mR / 2)))), mY + mR / 2,
                (float) (mX + Math.sqrt((mR * mR - (mR / 2) * (mR / 2)))), mY + mR / 2,
                middleArcColor, null, Shader.TileMode.MIRROR);
        mPaint.setShader(linearGradient);
        RectF oval = new RectF(mX - mR, mY - mR, mX + mR, mY + mR);
        canvas.drawArc(oval, startAngle, sweepAngle, false, mPaint);
        mPaint.setShader(null);
    }


    /**
     * 画总额度 底色
     *
     * @param canvas
     */
    private void drawArcMiddleBackground(Canvas canvas) {
        //笔的圆角
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setColor(middleArcColorBg);
        RectF oval = new RectF(mX - mR, mY - mR, mX + mR, mY + mR);
        canvas.drawArc(oval, startAngle, middleBgSweepAngle, false, mPaint);
    }

    private void drawArcInner(Canvas canvas) {
        mPaint.reset();
        mPaint.setAntiAlias(true);
        mPaint.setColor(innerArcColor);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(innerStrokeWith);
        RectF ovalInner = new RectF(mX - mRInner, mY - mRInner, mX + mRInner, mY + mRInner);
        //startAngle是三点钟方向的角度0,逆时针到起始的角度120
        canvas.drawArc(ovalInner, startAngle - 3, middleBgSweepAngle + 6, false, mPaint);
    }

    private void drawArcOut(Canvas canvas) {
        mPaint.reset();
        mPaint.setAntiAlias(true);
        mPaint.setColor(outerArcColor);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(outStrokeWith);
        RectF ovalOut = new RectF(mX - mROut, mY - mROut, mX + mROut, mY + mROut);
        canvas.drawArc(ovalOut, startAngle, middleBgSweepAngle, false, mPaint);
        //最外圈的两个边
        drawTwoBothSide(canvas);
    }

    /**
     * 画两边的耳朵
     *
     * @param canvas
     */
    private void drawTwoBothSide(Canvas canvas) {
        canvas.drawLine((float) (mX - mROut * Math.sin(Math.toRadians(openRadian / 2)))
                , (float) (mY + mROut * Math.cos(Math.toRadians(openRadian / 2)))
                , (float) (mX - mROut * Math.sin(Math.toRadians(openRadian / 2)) - bothEarLength)
                , (float) (mY + mROut * Math.cos(Math.toRadians(openRadian / 2))), mPaint);

        canvas.drawLine((float) (mX + mROut * Math.sin(Math.toRadians(openRadian / 2)))
                , (float) (mY + mROut * Math.cos(Math.toRadians(openRadian / 2)))
                , (float) (mX + mROut * Math.sin(Math.toRadians(openRadian / 2)) + bothEarLength)
                , (float) (mY + mROut * Math.cos(Math.toRadians(openRadian / 2))), mPaint);
    }

    private void drawMainCalibration(Canvas canvas) {
        float startAngele = (float) Math.toRadians(mStartAngle);
        for (int i = 0; i < mMainCalibration; i++) {
            float startX = (float) (mX + ((mR - mMainCalibrationLength - strokeWith / 2) * Math.cos(startAngele)));
            float startY = (float) (mY - ((mR - mMainCalibrationLength - strokeWith / 2) * Math.sin(startAngele)));
            float stopX = (float) (mX + (mR - strokeWith / 2) * Math.cos(startAngele));
            float stopY = (float) (mY - (mR - strokeWith / 2) * Math.sin(startAngele));
            canvas.drawLine(startX, startY, stopX, stopY, mPaint);
            startAngele = (float) (startAngele + Math.toRadians(30));
        }
    }


    private void drawSecondaryCalibration(Canvas canvas) {
        float startAngele = (float) Math.toRadians(mSecondaryStartAngle);
        for (int i = 0; i < mSecondaryCalibration; i++) {
            float startX = (float) (mX + ((mR - mSecondaryCalibrationLength - strokeWith) * Math.cos(startAngele)));
            float startY = (float) (mY - ((mR - mSecondaryCalibrationLength - strokeWith) * Math.sin(startAngele)));
            float stopX = (float) (mX + (mR - strokeWith) * Math.cos(startAngele));
            float stopY = (float) (mY - (mR - strokeWith) * Math.sin(startAngele));
            canvas.drawLine(startX, startY, stopX, stopY, mPaint);
            startAngele = (float) (startAngele + Math.toRadians(10));
        }
    }

    private void drawAvailableLimitAlias(Canvas canvas, String text) {
        mPaint.reset();
        mPaint.setColor(availableLimitTextColor);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setStrokeWidth(2);
        mPaint.setTextAlign(Paint.Align.CENTER);
        mPaint.setTextSize(20f);
        mPaint.setTextScaleX(1.4f);
        canvas.drawText(text, mX, mY - mR / 2, mPaint);
    }

    /**
     * 画可用额度
     *
     * @param canvas
     * @param text
     */
    private void drawTextAvailableLimit(Canvas canvas, String text) {
        mPaint.setFakeBoldText(true);
        mPaint.setTextSize(40);
        mPaint.setColor(defaultAvailableLimitColor);
        canvas.drawText(text, mX, mY - mR / 6, mPaint);
        //测量"钱"所占的像素
        int width = (int) mPaint.measureText(text);
        mPaint.setFakeBoldText(false);
        mPaint.setTextAlign(Paint.Align.LEFT);
        mPaint.setColor(currencySymbolColor);
        mPaint.setTextSize(26);
        //$符号的宽度,中心点的x坐标减去上一步画笔画字的宽度 再减去一个$的占位符,预留一点点位置
        int placeholderWidth = (int) mPaint.measureText("$");
        canvas.drawText("$", mX - width * 0.5f - placeholderWidth, mY - mR / 6, mPaint);
    }

    private void drawTotalLimitAlias(Canvas canvas, String text) {
        mPaint.setTextSize(16f);
        mPaint.setColor(totalLimitTextColor);
        mPaint.setTextAlign(Paint.Align.CENTER);
        canvas.drawText(text, mX, mY + mR / 20, mPaint);
    }

    /**
     * 画总额度
     *
     * @param canvas
     * @param text
     */
    private void drawTextTotalLimit(Canvas canvas, String text) {
        mPaint.setFakeBoldText(false);
        mPaint.setTextSize(24);
        mPaint.setColor(totalLimitColor);

        canvas.drawText(text, mX, mY + mR / 4, mPaint);
        //测量"钱"所占的像素
        int width = (int) mPaint.measureText(text);

        mPaint.setTextAlign(Paint.Align.LEFT);
        mPaint.setColor(currencySymbolColor);
        mPaint.setTextSize(18);
        //$符号的宽度
        int placeholderWidth = (int) mPaint.measureText("$");
        canvas.drawText("$", mX - width * 0.5f - placeholderWidth, mY + mR / 4, mPaint);
    }
}

下面是自定义的一些属性:

        
        
        
        
        
        
        
        
        
        
        
        
        
        
        

源码地址:

你可能感兴趣的:(圆形额度条(类似于清理大师的那个动画))