如何在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
相关产品推荐
相关产品推荐

