• 作者:老汪软件技巧
  • 发表时间:2024-11-15 15:02
  • 浏览量:

目标

自定义一个环形进度条,可以自定义其最大值、当前进度、背景色、进度色,宽度等信息。

最终效果如下(GIF展示纯色有点问题):

1. 结构分析2. 实现思路定义自定义属性:在 res/values/attrs.xml 中定义自定义属性,以便通过 XML 配置自定义的视图样式。初始化视图元素:在构造函数中,根据传入的属性初始化各种画笔、尺寸和视图元素。绘制视图内容:重写 onDraw 方法,使用画笔绘制背景圆环、进度圆环和文本。支持动态更新进度:提供 setProgress 方法,允许外部动态设置进度,触发视图重绘。3. 关键技术点解析

我们首先要知道,画圆的基础是有个正方形,或者说有一个正方形的坐标,还要考虑到画笔的宽度,所以我们首先就要确定矩形的大小:

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        int padding = circleWidth / 2;
        rectF.set(padding, padding, w - padding, h - padding);
    }

circleWidth 为圆环的宽,也为圆环画笔的宽。

canvas.drawArc是用于绘制弧形(圆弧)的一种方法,具体参数在前几篇文章已介绍,再次不做赘述。这里主要自定义了进度开始的角度,即 startAngle 属性。

textPaint.setTextAlign(Paint.Align.CENTER)

重点关注下文本画笔的这个属性,这个属性可以保证文本水平居中,而不需要手动去计算,文本的宽度信息,先来看下setTextAlign 的作用:

textPaint.setTextAlign(Paint.Align.CENTER) 是 Paint 类中的一个设置文本对齐方式的方法。它用于控制文本在指定位置绘制时的对齐方式,具体来说,它影响了绘制文本时文本的起始点(x,y 坐标)如何被确定。

作用:

Paint.Align 是一个枚举,定义了文本绘制时如何对齐相对于给定的 x 和 y 坐标。常用的对齐方式有三个选项:

Paint.Align.LEFT:

Paint.Align.CENTER:

Paint.Align.RIGHT:

由于只能做到水平居中,想做到垂直居中还是要计算基线位置的!基线的两种计算方式可以参考前几篇文章,里面有详细的介绍

4. 定义自定义属性

  <declare-styleable name="CircularProgressBar">
        
        <attr name="maxProgress" format="integer"/>
        
        <attr name="progress" format="integer"/>
        
        <attr name="circleBackgroundColor" format="color"/>
        
        <attr name="progressColor" format="color"/>
        
        <attr name="circleWidth" format="dimension"/>
        
        <attr name="showProgressText" format="boolean"/>
        
        <attr name="progressTextColor" format="color"/>
        
        <attr name="progressTextSize" format="dimension"/>
        
        <attr name="startAngle" format="enum">
            <enum name="angle0" value="0"/>
            <enum name="angle90" value="90"/>
            <enum name="angle180" value="180"/>
            <enum name="angle270" value="270"/>
        attr>
    declare-styleable>

5. 完整代码

public class CircularProgressBar extends View {
    private Paint backgroundPaint;
    private Paint progressPaint;
    private Paint textPaint;
    private RectF rectF;
    private int maxProgress = 100;
    private int progress = 0;
    private int circleBackgroundColor = Color.GRAY;
    private int progressColor = Color.GREEN;
    private int circleWidth = 20;
    private boolean showProgressText = true;
    private int progressTextColor = Color.BLACK;
    private int progressTextSize = 50;
    private int startAngle = 0; // 起始角度,默认从0开始
    public CircularProgressBar(Context context) {
        this(context, null);
    }
    public CircularProgressBar(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public CircularProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // 初始化自定义属性
        if (attrs != null) {
            TypedArray typedArray = context.getTheme().obtainStyledAttributes(
                    attrs,
                    R.styleable.CircularProgressBar,
                    0, 0
            );
            try {
                maxProgress = typedArray.getInt(R.styleable.CircularProgressBar_maxProgress, 100);
                progress = typedArray.getInt(R.styleable.CircularProgressBar_progress, 0);
                circleBackgroundColor = typedArray.getColor(R.styleable.CircularProgressBar_circleBackgroundColor, Color.GRAY);
                progressColor = typedArray.getColor(R.styleable.CircularProgressBar_progressColor, Color.GREEN);
                circleWidth = typedArray.getDimensionPixelSize(R.styleable.CircularProgressBar_circleWidth, 20);
                showProgressText = typedArray.getBoolean(R.styleable.CircularProgressBar_showProgressText, true);
                progressTextColor = typedArray.getColor(R.styleable.CircularProgressBar_progressTextColor, Color.BLACK);
                progressTextSize = typedArray.getDimensionPixelSize(R.styleable.CircularProgressBar_progressTextSize, 50);
                // 获取自定义的起始角度
                int angleValue = typedArray.getInt(R.styleable.CircularProgressBar_startAngle, 0);
                switch (angleValue) {
                    case 90:
                        startAngle = 90;
                        break;
                    case 180:
                        startAngle = 180;
                        break;
                    case 270:
                        startAngle = 270;
                        break;
                    default:
                        startAngle = 0;
                        break;
                }
            } finally {
                typedArray.recycle();
            }
        }

_进度环形图_圆环进度条

// 设置画笔属性 backgroundPaint = new Paint(); backgroundPaint.setColor(circleBackgroundColor); backgroundPaint.setStyle(Paint.Style.STROKE); backgroundPaint.setStrokeWidth(circleWidth); backgroundPaint.setAntiAlias(true); progressPaint = new Paint(); progressPaint.setColor(progressColor); progressPaint.setStyle(Paint.Style.STROKE); progressPaint.setStrokeWidth(circleWidth); progressPaint.setStrokeCap(Paint.Cap.ROUND);//圆角 progressPaint.setAntiAlias(true); textPaint = new Paint(); textPaint.setColor(progressTextColor); textPaint.setTextSize(progressTextSize); textPaint.setTextAlign(Paint.Align.CENTER);//文本对齐方式 textPaint.setAntiAlias(true); rectF = new RectF(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); int padding = circleWidth / 2; rectF.set(padding, padding, w - padding, h - padding); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 绘制背景圆环 canvas.drawArc(rectF, 0f, 360f, false, backgroundPaint); // 绘制进度圆环 float sweepAngle = 360f * progress / (float) maxProgress; // 根据起始角度调整起始位置 float adjustedStartAngle = 0f; switch (startAngle) { case 90: adjustedStartAngle = 0f; break; case 180: adjustedStartAngle = 90f; break; case 270: adjustedStartAngle = 180f; break; default: adjustedStartAngle = -90f; // 默认从0度(顶部)开始 break; } canvas.drawArc(rectF, adjustedStartAngle, sweepAngle, false, progressPaint); // 绘制进度文本 if (showProgressText) { String progressText = progress + "%"; // 使用视图的中心来计算x和y坐标 float x = getWidth() / 2f; float y = getHeight() / 2f - (textPaint.descent() + textPaint.ascent()) / 2f; // 绘制文本 canvas.drawText(progressText, x, y, textPaint); } } // 设置进度 public void setProgress(int progress) { if (progress > maxProgress) { this.progress = maxProgress; } else { this.progress = Math.max(progress, 0); } invalidate(); // 重新绘制 } // 获取当前进度 public int getProgress() { return progress; } }

6. 使用示例

xml:

<com.xaye.example.CircularProgressBar
        android:id="@+id/circularProgressBar"
        android:layout_width="140dp"
        android:layout_height="140dp"
        app:maxProgress="100"
        app:circleBackgroundColor="#DDDDDD"
        app:progressColor="#00B8D4"
        app:circleWidth="15dp"
        app:showProgressText="true"
        app:progressTextColor="#000000"
        app:progressTextSize="30sp"
        app:startAngle="angle0"/>

Activity:

val animator = ValueAnimator.ofInt(0, 80)
            animator.setDuration(2000)
            animator.interpolator = LinearInterpolator()
            animator.addUpdateListener { animation ->
                val value = animation.animatedValue as Int
                mBind.circularProgressBar.setProgress(value)
            }
            animator.start()

7. 最后

本篇没什么难度,主要是介绍点新的东西,再熟悉熟悉手感,再会。

另外给喜欢记笔记的同学安利一款好用的云笔记软件,对比大部分国内的这个算还不错的,免费好用:wolai