学而实习之 不亦乐乎

Android 实现圆形进度条(仪表盘)(二)

2024-01-04 20:13:07
public class CircleBarView extends View {

    private Paint outPaint, innerPaint;
    private Paint mTextPaint, mValueTextPaint, mUnitTextPaint;
    private final RectF oval = new RectF();

    //最大进度
    private int max = 100;

    //当前进度
    private int progress = 0;

    //文本内容
    private String text = "";

    //数值内容
    private String valueText = "";

    //单位内容
    private String unitText = "";

    // 圆弧颜色
    private int roundColor = ResourceUtil.getColor(R.color.color_f25);

    // 设置数值颜色
    private int valueColor = ResourceUtil.getColor(R.color.color_f25);

    //用于动画
    private float nowPro = 0;
    private ValueAnimator animator;

    // 文字间距
    private int textMargin;

    // 圆弧宽度、半径和数值、小间距
    private int roundWidth, radius, smallMargin;

    // view的高度
    private int height;

    // 中心点
    private final Point centerPoint = new Point();

    // 测量文字的范围
    private final Rect textRect = new Rect();

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

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

    public CircleBarView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAttrs(context);
    }

    public int getMax() {
        return max;
    }

    public void setMax(int max) {
        this.max = max;
    }

    public int getProgress() {
        return progress;
    }

    public void setProgress(int progress) {
        if (this.progress == progress) {
            return;
        }

        if (animator != null && animator.isRunning()) {
            animator.cancel();
        }

        animator = ValueAnimator.ofFloat(nowPro, progress);
        animator.setDuration(1000);
        animator.setInterpolator(new DecelerateInterpolator());
        animator.addUpdateListener(animation -> {
            nowPro = (float) animation.getAnimatedValue();
            CircleBarView.this.progress = (int) nowPro;
            valueText = String.valueOf(CircleBarView.this.progress);
            invalidate();
        });

        animator.start();
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public String getValueText() {
        return valueText;
    }

    public void setValueText(String valueText) {
        this.valueText = valueText;
    }

    public String getUnitText() {
        return unitText;
    }

    public void setUnitText(String unitText) {
        this.unitText = unitText;
    }

    public void setRoundColor(int roundColor) {
        this.roundColor = roundColor;
        invalidate();
    }

    public void setValueColor(int valueColor) {
        this.valueColor = valueColor;
        invalidate();
    }

    private void initAttrs(Context context) {
        // 文字间距
        textMargin = dp2px(context, 5);

        // 圆弧宽度
        roundWidth = dp2px(context, 5);

        // 半径
        radius = dp2px(context, 72);
        height = radius;

        // 小间距
        smallMargin = roundWidth / 2;

        outPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        outPaint.setColor(ResourceUtil.getColor(R.color.color_f6));
        outPaint.setStyle(Paint.Style.STROKE);
        outPaint.setStrokeCap(Paint.Cap.ROUND);
        outPaint.setStrokeWidth(roundWidth);

        innerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        innerPaint.setColor(ResourceUtil.getColor(R.color.color_f15));
        innerPaint.setStyle(Paint.Style.STROKE);
        innerPaint.setStrokeCap(Paint.Cap.ROUND);
        innerPaint.setStrokeWidth(roundWidth);

        mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mTextPaint.setColor(ResourceUtil.getColor(R.color.color_ff3));
        mTextPaint.setTextSize(sp2px(getContext(), 12));

        mValueTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mValueTextPaint.setColor(valueColor);
        mValueTextPaint.setTextSize(sp2px(getContext(), 24));

        mUnitTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mUnitTextPaint.setColor(ResourceUtil.getColor(R.color.color_ff3));
        mUnitTextPaint.setTextSize(sp2px(getContext(), 12));

        //动画
        animator = ValueAnimator.ofFloat(0, progress);
        animator.setDuration(1000);
        animator.setInterpolator(new DecelerateInterpolator());

        animator.addUpdateListener(animation -> {
            nowPro = (float) animation.getAnimatedValue();
            postInvalidate();
        });
        animator.start();
    }

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

        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);

        if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(widthSpecSize, (int) height);
        } else if (widthSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(widthSpecSize, heightSpecSize);
            height = heightSpecSize;
        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(widthSpecSize, (int) height);
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        // 中心点
        centerPoint.set(getWidth() / 2, height / 2);

        // 计算圆弧显示的范围,height * 2 是因为圆弧的高度是根据园来算的,所以双倍才是半个圆
        oval.set(getWidth() / 2f - radius, (float) roundWidth, getWidth() / 2f + radius, height * 2 - roundWidth*2);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        // 最外层圆弧
        canvas.drawArc(oval, 180, 180, false, outPaint); //绘制外层圆弧

        // 进度圆弧
        if (nowPro > 0) {
            innerPaint.setColor(roundColor);
        } else {
            innerPaint.setColor(ResourceUtil.getColor(R.color.color_f6));
        }

        canvas.drawArc(oval, 180, 180f * nowPro / max, false, innerPaint); //绘制圆弧

        // 文字从底部从下往上绘制。
        int valueY = height - smallMargin;

        // 值
        float valueTextWidth = mValueTextPaint.measureText(valueText);
        float unitTextWidth = mUnitTextPaint.measureText(unitText);
        canvas.drawText(valueText, centerPoint.x - (valueTextWidth + unitTextWidth) / 2f, valueY, mValueTextPaint);

        // 单位
        canvas.drawText(unitText, centerPoint.x + valueTextWidth / 2f - unitTextWidth / 2f + smallMargin, valueY, mUnitTextPaint);

        // 测量值的高度
        mValueTextPaint.getTextBounds(text, 0, text.length(), textRect);

        // 提示文字("高血压")
        float textWidth = mTextPaint.measureText(text);

        // 文字的高度 = 值的高度起点 - 值的高度 - 间距
        float textY = valueY - textRect.height() - textMargin;
        canvas.drawText(text, centerPoint.x - textWidth / 2f, textY, mTextPaint);
    }

    /**
     * dp 转 px
     */
    private int dp2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    /**
     * sp 转 px
     */
    private int sp2px(Context context, float spValue) {
        final float scale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (spValue * scale + 0.5f);
    }
}

使用方法

xml

<xxx.xxx.CircleBarView
android:id="@+id/circle_bar_view"
android:layout_width="match_parent"
android:layout_height="80dp"/>

activity中 :

circleBarView.setText("高风险");
circleBarView.setValueText("0");
circleBarView.setUnitText("分");
circleBarView.setProgress(100);