You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Android中用XML/Canvas实现带圆形百分比的自定义进度条?

实现思路

针对你需要的带圆形百分比文本的进度条,这里提供两种可行的实现方案,均无需使用Jetpack Compose:

方案一:自定义View(Canvas绘制,灵活度最高)

完全通过Canvas绘制进度条和圆形百分比区域,能精准匹配设计效果:

  • 1. 定义自定义属性
    在res/values/attrs.xml中添加可配置属性,方便在XML中调整样式:
<declare-styleable name="CustomProgressBar">
    <attr name="progressColor" format="color" />
    <attr name="barBgColor" format="color" />
    <attr name="circleStrokeColor" format="color" />
    <attr name="textColor" format="color" />
    <attr name="progress" format="integer" />
    <attr name="maxProgress" format="integer" />
</declare-styleable>
  • 2. 实现自定义View类
    重写onMeasure计算布局尺寸,onDraw分三部分绘制内容:
public class CustomProgressBar extends View {
    private Paint barBgPaint, progressPaint, circlePaint, textPaint;
    private int progress = 0;
    private int maxProgress = 100;
    private int circleDiameter;

    public CustomProgressBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CustomProgressBar);
        
        // 读取自定义属性
        int progressColor = ta.getColor(R.styleable.CustomProgressBar_progressColor, Color.parseColor("#2196F3"));
        int barBgColor = ta.getColor(R.styleable.CustomProgressBar_barBgColor, Color.parseColor("#E0E0E0"));
        int circleStrokeColor = ta.getColor(R.styleable.CustomProgressBar_circleStrokeColor, Color.parseColor("#2196F3"));
        int textColor = ta.getColor(R.styleable.CustomProgressBar_textColor, Color.BLACK);
        progress = ta.getInt(R.styleable.CustomProgressBar_progress, 0);
        maxProgress = ta.getInt(R.styleable.CustomProgressBar_maxProgress, 100);
        ta.recycle();

        // 初始化画笔
        circleDiameter = getResources().getDimensionPixelSize(R.dimen.circle_size);
        
        barBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        barBgPaint.setColor(barBgColor);
        
        progressPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        progressPaint.setColor(progressColor);
        
        circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        circlePaint.setColor(circleStrokeColor);
        circlePaint.setStyle(Paint.Style.STROKE);
        circlePaint.setStrokeWidth(2);
        
        textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        textPaint.setColor(textColor);
        textPaint.setTextSize(getResources().getDimensionPixelSize(R.dimen.progress_text_size));
        textPaint.setTextAlign(Paint.Align.CENTER);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int height = MeasureSpec.getSize(heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        // 确保预留圆形区域的宽度
        setMeasuredDimension(Math.max(width, circleDiameter), height);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int barHeight = getHeight() / 2;
        int barWidth = getWidth() - circleDiameter - 12; // 进度条宽度,减去圆形和间距
        float progressRatio = (float) progress / maxProgress;
        float filledWidth = barWidth * progressRatio;

        // 绘制进度条背景
        canvas.drawRoundRect(0, (getHeight() - barHeight)/2, barWidth, (getHeight() + barHeight)/2, barHeight/2, barHeight/2, barBgPaint);
        // 绘制进度条前景
        canvas.drawRoundRect(0, (getHeight() - barHeight)/2, filledWidth, (getHeight() + barHeight)/2, barHeight/2, barHeight/2, progressPaint);

        // 绘制圆形边框
        int circleCenterX = getWidth() - circleDiameter/2;
        int circleCenterY = getHeight()/2;
        canvas.drawCircle(circleCenterX, circleCenterY, circleDiameter/2 - 2, circlePaint);
        // 绘制百分比文本
        String percentText = (int)(progressRatio * 100) + "%";
        // 垂直居中计算:文本基线 = 圆心 - (文本 ascent + descent)/2
        float textBaseline = circleCenterY - (textPaint.descent() + textPaint.ascent()) / 2;
        canvas.drawText(percentText, circleCenterX, textBaseline, textPaint);
    }

    // 对外提供设置进度的方法
    public void setProgress(int progress) {
        this.progress = Math.min(Math.max(progress, 0), maxProgress);
        invalidate();
    }
}
  • 3. 在布局中使用
<com.yourpackage.CustomProgressBar
    android:id="@+id/customProgress"
    android:layout_width="match_parent"
    android:layout_height="40dp"
    android:layout_margin="16dp"
    app:progressColor="@color/your_progress_color"
    app:barBgColor="@color/your_bar_bg_color"
    app:progress="45"
    app:maxProgress="100" />

方案二:XML组合布局(快速实现)

用LinearLayout组合系统水平进度条和带圆形背景的TextView,适合对样式要求不极致的场景:

  • 1. 圆形背景Drawable(res/drawable/circle_bg.xml)
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <solid android:color="@color/white" />
    <stroke android:color="@color/your_stroke_color" android:width="2dp" />
</shape>
  • 2. 自定义进度条Drawable(res/drawable/progress_bar_bg.xml)
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@android:id/background">
        <shape android:shape="rectangle">
            <corners android:radius="10dp" />
            <solid android:color="@color/your_bar_bg_color" />
        </shape>
    </item>
    <item android:id="@android:id/progress">
        <clip>
            <shape android:shape="rectangle">
                <corners android:radius="10dp" />
                <solid android:color="@color/your_progress_color" />
            </shape>
        </clip>
    </item>
</layer-list>
  • 3. 布局XML
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="40dp"
    android:gravity="center_vertical"
    android:orientation="horizontal"
    android:paddingEnd="16dp"
    android:layout_margin="16dp">

    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="0dp"
        android:layout_height="20dp"
        android:layout_weight="1"
        android:progressDrawable="@drawable/progress_bar_bg"
        android:progress="45" />

    <TextView
        android:id="@+id/progressText"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_marginStart="12dp"
        android:background="@drawable/circle_bg"
        android:gravity="center"
        android:text="45%"
        android:textSize="14sp"
        android:textColor="@color/black" />
</LinearLayout>
  • 4. 代码同步进度
    设置进度时同时更新文本:
ProgressBar progressBar = findViewById(R.id.progressBar);
TextView progressText = findViewById(R.id.progressText);

int targetProgress = 60;
progressBar.setProgress(targetProgress);
progressText.setText(targetProgress + "%");

内容的提问来源于stack exchange,提问作者Monir Hossain

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 23:23:16