package com.dinuscxj.progressbar;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RadialGradient;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.SweepGradient;
import android.support.annotation.IntDef;
import android.util.AttributeSet;
import android.widget.ProgressBar;
public class CircleProgressBar extends ProgressBar {
private static final int LINE = 0;
private static final int SOLID = 1;
private static final int SOLID_LINE = 2;
private static final int LINEAR = 0;
private static final int RADIAL = 1;
private static final int SWEEP = 2;
private static final float DEFAULT_START_DEGREE = -90.0f;
private static final int DEFAULT_LINE_COUNT = 45;
private static final float DEFAULT_LINE_WIDTH = 4.0f;
private static final float DEFAULT_PROGRESS_TEXT_SIZE = 11.0f;
private static final float DEFAULT_PROGRESS_STROKE_WIDTH = 1.0f;
private static final String COLOR_FFF2A670 = "#fff2a670";
private static final String COLOR_FFD3D3D5 = "#ffe3e3e5";
private static final String DEFAULT_PATTERN = "%d%%";
private final RectF mProgressRectF = new RectF();
private final Rect mProgressTextRect = new Rect();
private final Paint mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint mProgressBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint mProgressTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private float mRadius;
private float mCenterX;
private float mCenterY;
//Background Color of the progress bar
private int mBackgroundColor;
//Only work well in the Line Style, represents the line count of the rings included
private int mLineCount;
//Only work well in the Line Style, Height of the line of the progress bar
private float mLineWidth;
//Stroke width of the progress of the progress bar
private float mProgressStrokeWidth;
//Text size of the progress of the progress bar
private float mProgressTextSize;
//Start color of the progress of the progress bar
private int mProgressStartColor;
//End color of the progress of the progress bar
private int mProgressEndColor;
//Color of the progress value of the progress bar
private int mProgressTextColor;
//Background color of the progress of the progress bar
private int mProgressBackgroundColor;
//If mDrawProgressText is true, will draw the progress text. otherwise, will not draw the progress text.
private boolean mDrawProgressText;
//Format the current progress value to the specified format
private String mProgressTextFormatPattern;
@Retention(RetentionPolicy.SOURCE)
@IntDef({LINE, SOLID, SOLID_LINE})
private @interface Style {
}
//The style of the progress color
@Style
private int mStyle;
@Retention(RetentionPolicy.SOURCE)
@IntDef({LINEAR, RADIAL, SWEEP})
private @interface ShaderMode {
}
//The Shader of mProgressPaint
@ShaderMode
private int mShader;
//The Stroke Cap of mProgressPaint and mProgressBackgroundPaint
private Paint.Cap mCap;
public CircleProgressBar(Context context) {
this(context, null);
}
public CircleProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
adjustIndeterminate();
initFromAttributes(context, attrs);
initPaint();
}
/**
* Basic data initialization
*/
@SuppressWarnings("ResourceType")
private void initFromAttributes(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleProgressBar);
mBackgroundColor = a.getColor(R.styleable.CircleProgressBar_background_color, Color.TRANSPARENT);
mDrawProgressText = a.getBoolean(R.styleable.CircleProgressBar_draw_progress_text, true);
mLineCount = a.getInt(R.styleable.CircleProgressBar_line_count, DEFAULT_LINE_COUNT);
mProgressTextFormatPattern = a.hasValue(R.styleable.CircleProgressBar_progress_text_format_pattern) ?
a.getString(R.styleable.CircleProgressBar_progress_text_format_pattern) : DEFAULT_PATTERN;
mStyle = a.getInt(R.styleable.CircleProgressBar_style, LINE);
mShader = a.getInt(R.styleable.CircleProgressBar_progress_shader, LINEAR);
mCap = a.hasValue(R.styleable.CircleProgressBar_progress_stroke_cap) ?
Paint.Cap.values()[a.getInt(R.styleable.CircleProgressBar_progress_stroke_cap, 0)] : Paint.Cap.BUTT;
mLineWidth = a.getDimensionPixelSize(R.styleable.CircleProgressBar_line_width, UnitUtils.dip2px(getContext(), DEFAULT_LINE_WIDTH));
mProgressTextSize = a.getDimensionPixelSize(R.styleable.CircleProgressBar_progress_text_size, UnitUtils.dip2px(getContext(), DEFAULT_PROGRESS_TEXT_SIZE));
mProgressStrokeWidth = a.getDimensionPixelSize(R.styleable.CircleProgressBar_progress_stroke_width, UnitUtils.dip2px(getContext(), DEFAULT_PROGRESS_STROKE_WIDTH));
mProgressStartColor = a.getColor(R.styleable.CircleProgressBar_progress_start_color, Color.parseColor(COLOR_FFF2A670));
mProgressEndColor = a.getColor(R.styleable.CircleProgressBar_progress_end_color, Color.parseColor(COLOR_FFF2A670));
mProgressTextColor = a.getColor(R.styleable.CircleProgressBar_progress_text_color, Color.parseColor(COLOR_FFF2A670));
mProgressBackgroundColor = a.getColor(R.styleable.CircleProgressBar_progress_background_color, Color.parseColor(COLOR_FFD3D3D5));
a.recycle();
}
/**
* Paint initialization
*/
private void initPaint() {
mProgressTextPaint.setTextAlign(Paint.Align.CENTER);
mProgressTextPaint.setTextSize(mProgressTextSize);
mProgressPaint.setStyle(mStyle == SOLID ? Paint.Style.FILL : Paint.Style.STROKE);
mProgressPaint.setStrokeWidth(mProgressStrokeWidth);
mProgressPaint.setColor(mProgressStartColor);
mProgressPaint.setStrokeCap(mCap);
mProgressBackgroundPaint.setStyle(mStyle == SOLID ? Paint.Style.FILL : Paint.Style.STROKE);
mProgressBackgroundPaint.setStrokeWidth(mProgressStrokeWidth);
mProgressBackgroundPaint.setColor(mProgressBackgroundColor);
mProgressBackgroundPaint.setStrokeCap(mCap);
mBackgroundPaint.setStyle(Paint.Style.FILL);
mBackgroundPaint.setColor(mBackgroundColor);
}
/**
* The progress bar color gradient,
* need to be invoked in the {@link #onSizeChanged(int, int, int, int)}
*/
private void updateProgressShader() {
if (mProgressStartColor != mProgressEndColor) {
Shader shader = null;
switch (mShader) {
case LINEAR:
shader = new LinearGradient(mProgressRectF.left, mProgressRectF.top,
mProgressRectF.left, mProgressRectF.bottom,
mProgressStartColor, mProgressEndColor, Shader.TileMode.CLAMP);
break;
case RADIAL:
shader = new RadialGradient(mCenterX, mCenterY, mRadius,
mProgressStartColor, mProgressEndColor, Shader.TileMode.CLAMP);
break;
case SWEEP:
//arc = radian * radius
float radian = (float) (mProgressStrokeWidth / Math.PI * 2.0f / mRadius);
float rotateDegrees = (float) (DEFAULT_START_DEGREE
- (mCap == Paint.Cap.BUTT && mStyle == SOLID_LINE ? 0 : Math.toDegrees(radian)));
shader = new SweepGradient(mCenterX, mCenterY, new int[] {mProgressStartColor, mProgressEndColor},
new float[] {0.0f, 1.0f});
Matrix matrix = new Matrix();
matrix.postRotate(rotateDegrees, mCenterX, mCenterY);
shader.setLocalMatrix(matrix);
break;
}
mProgressPaint.setShader(shader);
} else {
mProgressPaint.setShader(null);
mProgressPaint.setColor(mProgressStartColor);
}
}
/**
* In order to work well, need to modify some of the following fields through reflection.
* Another available way: write the following attributes to the xml
* <p>
* android:indeterminateOnly="false"
* android:indeterminate="false"
*/
private void adjustIndeterminate() {
try {
Field mOnlyIndeterminateField = ProgressBar.class.getDeclaredField("mOnlyIndeterminate");
mOnlyIndeterminateField.setAccessible(true);
mOnlyIndeterminateField.set(this, false);
Field mIndeterminateField = ProgressBar.class.getDeclaredField("mIndeterminate");
mIndeterminateField.setAccessible(true);
mIndeterminateField.set(this, false);
Field mCurrentDrawableField = ProgressBar.class.getDeclaredField("mCurrentDrawable");
mCurrentDrawableField.setAccessible(true);
mCurrentDrawableField.set(this, null);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
@Override
protected synchronized void onDraw(Canvas canvas) {
drawBackground(canvas);
drawProgress(canvas);
drawProgressText(canvas);
}
private void drawBackground(Canvas canvas) {
if (mBackgroundColor != Color.TRANSPARENT) {
canvas.drawCircle(mCenterX, mCenterX, mRadius, mBackgroundPaint);
}
}
private void drawProgressText(Canvas canvas) {
if (!mDrawProgressText) {
return;
}
String progressText = String.format(mProgressTextFormatPattern, getProgress());
mProgressTextPaint.setTextSize(mProgressTextSize);
mProgressTextPaint.setColor(mProgressTextColor);
mProgressTextPaint.getTextBounds(progressText, 0, progressText.length(), mProgressTextRect);
canvas.drawText(progressText, mCenterX, mCenterY + mProgressTextRect.height() / 2, mProgressTextPaint);
}
private void drawProgress(Canvas canvas) {
switch (mStyle) {
case SOLID:
drawSolidProgress(canvas);
break;
case SOLID_LINE:
drawSolidLineProgress(canvas);
break;
case LINE:
default:
drawLineProgress(canvas);
break;
}
}
/**
* In the center of the drawing area as a reference point , rotate the canvas
*/
private void drawLineProgress(Canvas canvas) {
float unitDegrees = (float) (2.0f * Math.PI / mLineCount);
float outerCircleRadius = mRadius;
float interCircleRadius = mRadius - mLineWidth;
int progressLineCount = (int) ((float) getProgress() / (float) getMax() * mLineCount);
for (int i = 0; i < mLineCount; i++) {
float rotateDegrees = i * unitDegrees;
float startX = mCenterX + (float) Math.sin(rotateDegrees) * interCircleRadius;
float startY = mCenterX - (float) Math.cos(rotateDegrees) * interCircleRadius;
float stopX = mCenterX + (float) Math.sin(rotateDegrees) * outerCircleRadius;
float stopY = mCenterX - (float) Math.cos(rotateDegrees) * outerCircleRadius;
if (i < progressLineCount) {
canvas.drawLine(startX, startY, stopX, stopY, mProgressPaint);
} else {
canvas.drawLine(startX, startY, stopX, stopY, mProgressBackgroundPaint);
}
}
}
/**
* Just draw arc
*/
private void drawSolidProgress(Canvas canvas) {
canvas.drawArc(mProgressRectF, DEFAULT_START_DEGREE, 360.0f, false, mProgressBackgroundPaint);
canvas.drawArc(mProgressRectF, DEFAULT_START_DEGREE, 360.0f * getProgress() / getMax(), true, mProgressPaint);
}
/**
* Just draw arc
*/
private void drawSolidLineProgress(Canvas canvas) {
canvas.drawArc(mProgressRectF, DEFAULT_START_DEGREE, 360.0f, false, mProgressBackgroundPaint);
canvas.drawArc(mProgressRectF, DEFAULT_START_DEGREE, 360.0f * getProgress() / getMax(), false, mProgressPaint);
}
/**
* When the size of CircleProgressBar changed, need to re-adjust the drawing area
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mCenterX = w / 2;
mCenterY = h / 2;
mRadius = Math.min(mCenterX, mCenterY);
mProgressRectF.top = mCenterY - mRadius;
mProgressRectF.bottom = mCenterY + mRadius;
mProgressRectF.left = mCenterX - mRadius;
mProgressRectF.right = mCenterX + mRadius;
updateProgressShader();
//Prevent the progress from clipping
mProgressRectF.inset(mProgressStrokeWidth / 2, mProgressStrokeWidth / 2);
}
public int getBackgroundColor() {
return mBackgroundColor;
}
public void setBackgroundColor(int backgroundColor) {
this.mBackgroundColor = backgroundColor;
mBackgroundPaint.setColor(backgroundColor);
invalidate();
}
public void setProgressTextFormatPattern(String progressTextformatPattern) {
this.mProgressTextFormatPattern = progressTextformatPattern;
invalidate();
}
public String getProgressTextFormatPattern() {
return mProgressTextFormatPattern;
}
public void setProgressStrokeWidth(float progressStrokeWidth) {
this.mProgressStrokeWidth = progressStrokeWidth;
mProgressRectF.inset(mProgressStrokeWidth / 2, mProgressStrokeWidth / 2);
invalidate();
}
public float getProgressStrokeWidth() {
return mProgressStrokeWidth;
}
public void setProgressTextSize(float progressTextSize) {
this.mProgressTextSize = progressTextSize;
invalidate();
}
public float getProgressTextSize() {
return mProgressTextSize;
}
public void setProgressStartColor(int progressStartColor) {
this.mProgressStartColor = progressStartColor;
updateProgressShader();
invalidate();
}
public int getProgressStartColor() {
return mProgressStartColor;
}
public void setProgressEndColor(int progressEndColor) {
this.mProgressEndColor = progressEndColor;
updateProgressShader();
invalidate();
}
public int getProgressEndColor() {
return mProgressEndColor;
}
public void setProgressTextColor(int progressTextColor) {
this.mProgressTextColor = progressTextColor;
invalidate();
}
public int getProgressTextColor() {
return mProgressTextColor;
}
public void setProgressBackgroundColor(int progressBackgroundColor) {
this.mProgressBackgroundColor = progressBackgroundColor;
mProgressBackgroundPaint.setColor(mProgressBackgroundColor);
invalidate();
}
public int getProgressBackgroundColor() {
return mProgressBackgroundColor;
}
public int getLineCount() {
return mLineCount;
}
public void setLineCount(int lineCount) {
this.mLineCount = lineCount;
invalidate();
}
public float getLineWidth() {
return mLineWidth;
}
public void setLineWidth(float lineWidth) {
this.mLineWidth = lineWidth;
invalidate();
}
public int getStyle() {
return mStyle;
}
public void setStyle(@Style int style) {
this.mStyle = style;
mProgressPaint.setStyle(mStyle == SOLID ? Paint.Style.FILL : Paint.Style.STROKE);
mProgressBackgroundPaint.setStyle(mStyle == SOLID ? Paint.Style.FILL : Paint.Style.STROKE);
invalidate();
}
public int getShader() {
return mShader;
}
public void setShader(@ShaderMode int shader) {
mShader = shader;
updateProgressShader();
invalidate();
}
public Paint.Cap getCap() {
return mCap;
}
public void setCap(Paint.Cap cap) {
mCap = cap;
mProgressPaint.setStrokeCap(cap);
mProgressBackgroundPaint.setStrokeCap(cap);
invalidate();
}
}