- 这个需求,在我进上上家公司,做第一个项目的时候就遇到了。当时百度了一下,找到了夏安明童鞋的圆形进度条的实现。然后copy过去,很愉快地就解决了需求。很happy的样子。
- 前几天,忽然想弄一下圆形进度条,于是,就弄了一个。记录在此,纪念一下。
- 为了方便使用和扩展,我也弄了几个自定义属性
- 可以自定义进度条颜色
- 可以自定义进度条边框颜色
- 可以自定义进度条宽度
- 可以自定义有无边框
- 可以自定义文字颜色
- 可以自定义文字大小(这个貌似没有考虑边界情况)
- 可以自定义文字是否显示
- 可以自定义进度条是环形,还是扇形
- 自定义属性文件如下
attr.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="progress" format="reference|integer"/>
<attr name="progressWidth" format="dimension|reference|float"/>
<attr name="bordersWidth" format="dimension|reference|float"/>
<attr name="borderVisible" format="boolean"/>
<attr name="progressColor" format="color|reference|integer"/>
<attr name="borderColor" format="color|reference|integer"/>
<attr name="textColor" format="color|reference|integer"/>
<attr name="textSize" format="dimension|reference|float"/>
<attr name="textVisible" format="boolean"/>
<attr name="useCenter" format="boolean"/>
<declare-styleable name="CircleProgressBar">
<attr name="progress"/>
<attr name="progressWidth"/>
<attr name="bordersWidth"/>
<attr name="borderVisible"/>
<attr name="progressColor"/>
<attr name="borderColor"/>
<attr name="textColor"/>
<attr name="textSize"/>
<attr name="textVisible"/>
<attr name="useCenter"/>
</declare-styleable>
</resources>
- 然后代码如下:
/**
* Created on 2017/1/21.
*/
public class CircleProgressBar extends View {
private final boolean useCenter;
private int progress;
private final int borderColor;
private final float borderWidth;
private final boolean textVisible;
private float progressWidth;
private int progressColor;
private boolean borderVisible;
private final int textColor;
private float textSize;
private Paint mPaint;
private int mWidth;
private int mHeight;
private int mRadius;
private Rect bounds;
private RectF oval;
public CircleProgressBar(Context context) {
this(context, null);
}
public CircleProgressBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CircleProgressBar, 0, 0);
// progress
progress = array.getInt(R.styleable.CircleProgressBar_progress, 35);
progressWidth = array.getDimension(R.styleable.CircleProgressBar_progressWidth, 20);
Log.d("A", "progressWidth = " + progressWidth);
progressColor = array.getColor(R.styleable.CircleProgressBar_progressColor, Color.BLUE);
// border
borderColor = array.getColor(R.styleable.CircleProgressBar_borderColor, Color.GRAY);
borderWidth = array.getDimension(R.styleable.CircleProgressBar_bordersWidth, dp2Px(context, 0.3f));
Log.d("A", "borderWidth = " + borderWidth);
borderVisible = array.getBoolean(R.styleable.CircleProgressBar_borderVisible, true);
// text
// textSize --> todo
textSize = array.getDimension(R.styleable.CircleProgressBar_textSize, dp2Px(context, 12));
Log.d("A", "textSize = " + textSize);
textColor = array.getColor(R.styleable.CircleProgressBar_textColor, Color.RED);
textVisible = array.getBoolean(R.styleable.CircleProgressBar_textVisible, true);
useCenter = array.getBoolean(R.styleable.CircleProgressBar_useCenter, false);
array.recycle();
}
public static int dp2Px(Context context, float dp) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dp * scale + 0.5f);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
init(w, h);
}
private void init(int w, int h) {
this.mWidth = w;
this.mHeight = w;
this.mRadius = Math.min(w, h) / 2;
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStrokeCap(Paint.Cap.ROUND);
bounds = new Rect();
oval = new RectF();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width;
int height;
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
width = widthSize / 2;
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
height = heightSize / 2;
}
setMeasuredDimension(width, height);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 1. drawRing
drawRing(canvas);
// 2. drawText
drawText(canvas);
// 3.drawProgress
drawProgress(canvas);
}
private void drawProgress(Canvas canvas) {
mPaint.setColor(progressColor);
if (useCenter) {
oval.set(0, 0, mRadius * 2, mRadius * 2);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setStrokeWidth(0);
} else {
float proRadius = mRadius - borderWidth - progressWidth / 2;
oval.set(mRadius - proRadius, mRadius - proRadius, mRadius + proRadius, mRadius + proRadius);
mPaint.setStrokeWidth(progressWidth);
mPaint.setStyle(Paint.Style.STROKE);
}
mPaint.setAntiAlias(false);
mPaint.setStrokeCap(Paint.Cap.ROUND);
canvas.drawArc(oval, -90, progress * 1f / 100 * 360, useCenter, mPaint);
}
private void drawText(Canvas canvas) {
if (textVisible) {
mPaint.setStrokeWidth(0);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setColor(textColor);
mPaint.setTextSize(textSize); // todo ->
mPaint.setTextAlign(Paint.Align.CENTER); // 文字对齐方式为文字的中间(底部中点)
String text;
text = "" + progress + "%";
mPaint.getTextBounds(text, 0, text.length(), bounds);
Log.d("A", "" + bounds);
// 让文字居中 ("要显示的文字",view横轴中点,view纵轴中点+文字高度/2,画笔)
canvas.drawText(text, mRadius, mRadius + bounds.height() / 2, mPaint);
}
}
private void drawRing(Canvas canvas) {
if (borderVisible) {
mPaint.setColor(borderColor);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(borderWidth);
canvas.drawCircle(mWidth / 2, mHeight / 2, mRadius, mPaint); // outer circle
canvas.drawCircle(mWidth / 2, mHeight / 2,
mRadius - progressWidth - borderWidth, mPaint); // inner circle
}
}
public synchronized void setProgress(@IntRange(from = 0, to = 100) int progress) {
this.progress = progress;
postInvalidate();
}
}
代码不多,不到200行。使用更简单
<com.abc.circleprogressbar.CircleProgressBar
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/circle_progressBar"
android:layout_width="80dp"
android:layout_height="80dp"
app:borderColor="@color/colorPrimary"
app:borderVisible="true"
app:bordersWidth="1dp"
app:progress="0"
app:progressColor="@color/cale_actionbar_tab_color"
app:progressWidth="8dp"
app:textColor="@color/colorPrimaryDark"
app:textSize="14sp"
app:textVisible="false"
app:useCenter="true"
/>
- 主要解释一下
drawText(Canvas canvas)
方法:
private void drawText(Canvas canvas) {
if (textVisible) {
mPaint.setStrokeWidth(0);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setColor(textColor);
mPaint.setTextSize(textSize); // todo ->
mPaint.setTextAlign(Paint.Align.CENTER); // 文字对齐方式为文字的中间(底部中点)
String text;
text = "" + progress + "%";
mPaint.getTextBounds(text, 0, text.length(), bounds);
Log.d("A", "" + bounds);
// 让文字居中 ("要显示的文字",view横轴中点,view纵轴中点+文字高度/2,画笔)
canvas.drawText(text, mRadius, mRadius + bounds.height() / 2, mPaint);
}
}
通过以上方式就可以让文字居中显示了。因为上述方式,首先将text的对齐坐标点,从左下角,移动到了底部中点位置。(是的,text的对齐坐标是左下角的点,而不是常见的左上角)。然后,将对齐坐标点放到view中间,让后向下移动半个text高度即可。这样子就让text完完全全的呈现在view的正中间了。
彩蛋时刻:为了方便使用,已将代码开源到github,我的第一个开源库发布了
使用非常简单
allprojects { repositories { maven { url 'https://jitpack.io' } } } compile 'com.github.duckAndroid:circleProgressbar:0.0.2'
布局文件中使用方式如下:
<com.pythoncat.circleprogressbar.CircleProgressBar xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/circle_progressBar" android:layout_width="80dp" android:layout_height="80dp" app:borderColor="@color/colorPrimary" app:borderVisible="true" app:bordersWidth="1dp" app:progress="0" app:progressColor="@color/colorAccent" app:progressWidth="8dp" app:textColor="@color/colorPrimaryDark" app:textSize="14sp" app:textVisible="false" app:useCenter="true"/>
彩蛋结束……………………