Skip to content
This repository was archived by the owner on May 24, 2022. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ circularProgress.setProgress(5000, 10000);
// you can get progress values using following getters
circularProgress.getProgress() // returns 5000
circularProgress.getMaxProgress() // returns 10000

// you can also set a visual gap between the progress and background strokes
circularProgress.setProgressGap(100);
```

#### Attributes
Expand All @@ -105,6 +108,7 @@ circularProgress.getMaxProgress() // returns 10000
| Dot width | `app:dotWidth` | setters: `setDotWidthDp(widthInDp)` or `setDotWidthPx(widthInPx)`<br/>getter: `getDotWidth()` (returns width in pixels) | same as progress stroke width |
| Progress text size | `app:textSize` | setters: `setTextSizeSp(sizeInSp)` or `setTextSizePx(sizeInPx)`<br/>getter: `getTextSize()` (returns size in pixels) | `24sp` |
| Progress text color | `app:textColor` | setter: `setTextColor(textColor)`<br/>getter: `getTextColor()` | same as progress color |
| Whether to show text | `app:showText` | setter: `setShowTextEnabled(enabled)`<br/>getter: `isShowTextEnabled()` | `true` |
| Formatting pattern to be used in `PatternProgressTextAdapter`. Checkout [Formatting progress text](#formatting-progress-text) section. | `app:formattingPattern` | setter: `setProgressTextAdapter(progressTextAdapter)`<br/>getter: `getProgressTextAdapter()` | not specified |
| Direction of the progress arc (`clockwise` or `counterclockwise`) | `app:direction` | setter: `setDirection(direction)`<br/>getter: `getDirection()` | `counterclockwise` |
| Start angle. Checkout [Start angle](#setting-start-angle) section. | `app:startAngle` | setter: `setStartAngle(startAngle)`<br/>getter: `getStartAngle()` | `270` |
Expand Down Expand Up @@ -245,6 +249,11 @@ circularProgress.setGradient(gradientType, endColor);
circularProgress.getGradientType(); //returns LINEAR_GRADIENT
```

Or, for advanced gradients with multiple colors and optional color positions, in code:
```java
circularProgress.setGradient(type, new int[]{Color.BLUE, Color.MAGENTA, Color.RED}, new float[]{0f, .3f, .7f});
```

---

### Download using Gradle
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.3'
classpath 'com.android.tools.build:gradle:7.1.2'
classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0'

// NOTE: Do not place your application dependencies here; they belong
Expand Down
8 changes: 2 additions & 6 deletions circularprogressindicator/build.gradle
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
apply plugin: 'com.android.library'
apply plugin: 'com.github.dcendents.android-maven'
group='com.github.antonKozyriatskyi.CircularProgressIndicator'

android {
compileSdkVersion 27
compileSdkVersion 31

defaultConfig {
minSdkVersion 15
targetSdkVersion 27
versionCode 1
versionName "1.0"
}

buildTypes {
Expand All @@ -23,5 +19,5 @@ android {

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:support-annotations:27.1.1'
implementation 'androidx.annotation:annotation:1.3.0'
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import android.animation.PropertyValuesHolder;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
Expand All @@ -18,12 +19,6 @@
import android.graphics.Shader;
import android.graphics.SweepGradient;
import android.os.Build;
import android.support.annotation.ColorInt;
import android.support.annotation.Dimension;
import android.support.annotation.IntDef;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
Expand All @@ -32,14 +27,22 @@
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;

import androidx.annotation.ColorInt;
import androidx.annotation.Dimension;
import androidx.annotation.IntDef;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Size;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
* Created by Anton on 03.03.2018.
*/

@SuppressWarnings("FieldCanBeLocal")
@SuppressWarnings({"FieldCanBeLocal", "unused"})
public class CircularProgressIndicator extends View {

public static final int DIRECTION_CLOCKWISE = 0;
Expand Down Expand Up @@ -75,7 +78,7 @@ public class CircularProgressIndicator extends View {
private Paint textPaint;

private int startAngle = DEFAULT_PROGRESS_START_ANGLE;
private int sweepAngle = 0;
private float sweepAngle = 0;

private RectF circleBounds;

Expand All @@ -89,16 +92,22 @@ public class CircularProgressIndicator extends View {

private double maxProgressValue = 100.0;
private double progressValue = 0.0;
private double progressGap = 0.0;

private boolean isAnimationEnabled;

private boolean isFillBackgroundEnabled;

private int animationDuration = DEFAULT_ANIMATION_DURATION;

private boolean isShowTextEnabled = true;

@Direction
private int direction = DIRECTION_COUNTERCLOCKWISE;

private ValueAnimator progressAnimator;

@SuppressWarnings("NotNullFieldNotInitialized") // initialized in init method
@NonNull
private ProgressTextAdapter progressTextAdapter;

Expand All @@ -108,6 +117,9 @@ public class CircularProgressIndicator extends View {
@NonNull
private Interpolator animationInterpolator = new AccelerateDecelerateInterpolator();

@NonNull
private final Rect textBoundsRect = new Rect();

public CircularProgressIndicator(Context context) {
super(context);
init(context, null);
Expand Down Expand Up @@ -165,6 +177,7 @@ private void init(@NonNull Context context, @Nullable AttributeSet attrs) {

isAnimationEnabled = a.getBoolean(R.styleable.CircularProgressIndicator_enableProgressAnimation, true);
isFillBackgroundEnabled = a.getBoolean(R.styleable.CircularProgressIndicator_fillBackground, false);
isShowTextEnabled = a.getBoolean(R.styleable.CircularProgressIndicator_showText, true);

direction = a.getInt(R.styleable.CircularProgressIndicator_direction, DIRECTION_COUNTERCLOCKWISE);

Expand All @@ -188,12 +201,7 @@ private void init(@NonNull Context context, @Nullable AttributeSet attrs) {
throw new IllegalArgumentException("did you forget to specify gradientColorEnd?");
}

post(new Runnable() {
@Override
public void run() {
setGradient(gradientType, gradientColorEnd);
}
});
post(() -> setGradient(gradientType, gradientColorEnd));
}

a.recycle();
Expand All @@ -208,6 +216,7 @@ public void run() {

Paint.Style progressBackgroundStyle = isFillBackgroundEnabled ? Paint.Style.FILL_AND_STROKE : Paint.Style.STROKE;
progressBackgroundPaint = new Paint();
progressBackgroundPaint.setStrokeCap(progressStrokeCap);
progressBackgroundPaint.setStyle(progressBackgroundStyle);
progressBackgroundPaint.setStrokeWidth(progressBackgroundStrokeWidth);
progressBackgroundPaint.setColor(progressBackgroundColor);
Expand All @@ -229,6 +238,7 @@ public void run() {
circleBounds = new RectF();
}

@SuppressLint("SwitchIntDef")
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Expand All @@ -244,10 +254,8 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);

Rect textBoundsRect = new Rect();
textPaint.getTextBounds(progressText, 0, progressText.length(), textBoundsRect);


float dotWidth = dotPaint.getStrokeWidth();
float progressWidth = progressPaint.getStrokeWidth();
float progressBackgroundWidth = progressBackgroundPaint.getStrokeWidth();
Expand Down Expand Up @@ -335,12 +343,26 @@ protected void onDraw(Canvas canvas) {
drawProgressBackground(canvas);
drawProgress(canvas);
if (shouldDrawDot) drawDot(canvas);
drawText(canvas);
if (isShowTextEnabled) drawText(canvas);
}

private void drawProgressBackground(Canvas canvas) {
canvas.drawArc(circleBounds, ANGLE_START_PROGRESS_BACKGROUND, ANGLE_END_PROGRESS_BACKGROUND,
false, progressBackgroundPaint);
if (progressGap == 0) {
canvas.drawArc(circleBounds, ANGLE_START_PROGRESS_BACKGROUND, ANGLE_END_PROGRESS_BACKGROUND,
false, progressBackgroundPaint);
} else {
float gapAngle = (float) (progressGap / maxProgressValue * 360);

float startAngle = this.sweepAngle + this.startAngle + gapAngle;
float sweepAngle = 360 - this.sweepAngle - 2 * gapAngle;

if (sweepAngle < 0) {
return;
}

canvas.drawArc(circleBounds, startAngle, sweepAngle,
false, progressBackgroundPaint);
}
}

private void drawProgress(Canvas canvas) {
Expand Down Expand Up @@ -403,34 +425,27 @@ public void setProgress(double current, double max) {
if (isAnimationEnabled) {
startProgressAnimation(oldCurrentProgress, finalAngle);
} else {
sweepAngle = (int) finalAngle;
sweepAngle = (float) finalAngle;
invalidate();
}
}

private void startProgressAnimation(double oldCurrentProgress, final double finalAngle) {
final PropertyValuesHolder angleProperty = PropertyValuesHolder.ofInt(PROPERTY_ANGLE, sweepAngle, (int) finalAngle);
final PropertyValuesHolder angleProperty = PropertyValuesHolder.ofFloat(PROPERTY_ANGLE, sweepAngle, (float) finalAngle);

progressAnimator = ValueAnimator.ofObject(new TypeEvaluator<Double>() {
@Override
public Double evaluate(float fraction, Double startValue, Double endValue) {
return (startValue + (endValue - startValue) * fraction);
}
}, oldCurrentProgress, progressValue);
progressAnimator.setDuration(DEFAULT_ANIMATION_DURATION);
progressAnimator = ValueAnimator.ofObject((TypeEvaluator<Double>)
(fraction, startValue, endValue) -> (startValue + (endValue - startValue) * fraction), oldCurrentProgress, progressValue);
progressAnimator.setDuration(animationDuration);
progressAnimator.setValues(angleProperty);
progressAnimator.setInterpolator(animationInterpolator);
progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
sweepAngle = (int) animation.getAnimatedValue(PROPERTY_ANGLE);
invalidate();
}
progressAnimator.addUpdateListener(animation -> {
sweepAngle = (float) animation.getAnimatedValue(PROPERTY_ANGLE);
invalidate();
});
progressAnimator.addListener(new DefaultAnimatorListener() {
@Override
public void onAnimationCancel(Animator animation) {
sweepAngle = (int) finalAngle;
sweepAngle = (float) finalAngle;
invalidate();
progressAnimator = null;
}
Expand Down Expand Up @@ -621,7 +636,6 @@ public float getDotWidth() {
return dotPaint.getStrokeWidth();
}


public double getProgress() {
return progressValue;
}
Expand Down Expand Up @@ -658,6 +672,7 @@ public void setProgressStrokeCap(@Cap int cap) {
Paint.Cap paintCap = (cap == CAP_ROUND) ? Paint.Cap.ROUND : Paint.Cap.BUTT;
if (progressPaint.getStrokeCap() != paintCap) {
progressPaint.setStrokeCap(paintCap);
progressBackgroundPaint.setStrokeCap(paintCap);
invalidate();
}
}
Expand Down Expand Up @@ -696,6 +711,24 @@ public boolean isFillBackgroundEnabled() {
return isFillBackgroundEnabled;
}

public void setShowTextEnabled(boolean enabled) {
if (isShowTextEnabled == enabled) return;
isShowTextEnabled = enabled;
invalidateEverything();
}

public boolean isShowTextEnabled() {
return isShowTextEnabled;
}

public void setAnimationDuration(int duration) {
animationDuration = duration;
}

public int getAnimationDuration() {
return animationDuration;
}

public void setInterpolator(@NonNull Interpolator interpolator) {
animationInterpolator = interpolator;
}
Expand All @@ -706,23 +739,28 @@ public Interpolator getInterpolator() {
}

public void setGradient(@GradientType int type, @ColorInt int endColor) {
int startColor = progressPaint.getColor();
setGradient(type, new int[]{startColor, endColor}, null);
}

public void setGradient(@GradientType int type, @Size(min = 2) @ColorInt int[] colors, @Nullable float[] positions) {
Shader gradient = null;

float cx = getWidth() / 2f;
float cy = getHeight() / 2f;

int startColor = progressPaint.getColor();

switch (type) {
case LINEAR_GRADIENT:
gradient = new LinearGradient(0f, 0f, getWidth(), getHeight(), startColor, endColor, Shader.TileMode.CLAMP);
gradient = new LinearGradient(0f, 0f, getWidth(), getHeight(), colors, positions, Shader.TileMode.CLAMP);
break;
case RADIAL_GRADIENT:
gradient = new RadialGradient(cx, cy, cx, startColor, endColor, Shader.TileMode.MIRROR);
gradient = new RadialGradient(cx, cy, cx, colors, positions, Shader.TileMode.MIRROR);
break;
case SWEEP_GRADIENT:
gradient = new SweepGradient(cx, cy, new int[]{startColor, endColor}, null);
gradient = new SweepGradient(cx, cy, colors, positions);
break;
case NO_GRADIENT:
return;
}

if (gradient != null) {
Expand Down Expand Up @@ -753,6 +791,15 @@ public int getGradientType() {
return type;
}

public double getProgressGap() {
return progressGap;
}

public void setProgressGap(double progressGap) {
this.progressGap = progressGap;
invalidate();
}

@Retention(RetentionPolicy.SOURCE)
@IntDef({DIRECTION_CLOCKWISE, DIRECTION_COUNTERCLOCKWISE})
public @interface Direction {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package antonkozyriatskyi.circularprogressindicator;

import androidx.annotation.NonNull;

/**
* Created by Anton on 06.06.2018.
*/

public final class DefaultProgressTextAdapter implements CircularProgressIndicator.ProgressTextAdapter {

@NonNull
@Override
public String formatText(double currentProgress) {
return String.valueOf((int) currentProgress);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package antonkozyriatskyi.circularprogressindicator;

import android.support.annotation.NonNull;
import androidx.annotation.NonNull;

/**
* Created by Anton on 06.06.2018.
*/

public final class PatternProgressTextAdapter implements CircularProgressIndicator.ProgressTextAdapter {

private String pattern;
private final String pattern;

public PatternProgressTextAdapter(String pattern) {
this.pattern = pattern;
Expand Down
Loading