diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..11de0b8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,34 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: GuilhE + +--- + +## Describe the bug +A clear and concise description of what the bug is. + +## To Reproduce +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +## Expected behavior +A clear and concise description of what you expected to happen. + +## Screenshots +If applicable, add screenshots to help explain your problem. + +## Details (please complete the following information): +- Library version +- Android version +- Emulator/Device specs +- Logs/Crash Reports/Stacktraces +- Example Code/Link to repositories + +## Additional context +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..aa1ea7b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +## Is your feature request related to a problem? Please describe +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +## Describe the solution you'd like +A clear and concise description of what you want to happen. + +## Describe alternatives you've considered +A clear and concise description of any alternative solutions or features you've considered. + +## Additional context +Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/pull_request.md b/.github/ISSUE_TEMPLATE/pull_request.md new file mode 100644 index 0000000..0f6a964 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/pull_request.md @@ -0,0 +1,19 @@ +--- +name: Pull Request +about: Contribute to this project +title: '' +labels: '' +assignees: '' + +--- + +## Description +A few sentences describing the overall goals of the pull request's commits. + +## Related PRs/Issues +List related PRs and Issues for this Pull Request. + +## Todos +- [ ] Tests +- [ ] Documentation +- [ ] Screenshots diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..ad14d8e --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,25 @@ +name: Publish to Bintray +on: + release: + types: [published] + +jobs: + publish: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + + - name: Grant Permission to Execute + run: chmod +x gradlew + + - name: Publish Library + env: + bintrayUser: ${{ secrets.BINTRAY_USER }} + bintrayApiKey: ${{ secrets.BINTRAY_API_KEY }} + run: ./gradlew bintrayUpload \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ad779e2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,32 @@ +# Changelog + +--- + +## [2.0.0] + +- Added thumb size configurations support +- Kotlin port +- Global repository update +- Changed: `setProgressAnimationCallback` to `setActionCallback` +- Changed: `setBackgroundColor` to `setProgressBackgroundColor` +- Changed: `setProgressThumbSizeRate` to `setProgressMaxThumbSizeRate` +- Changed: `` to `` +- Changed: `` to `` + +--- + +## [1.4.1] + +- Changed LIBRARY_PACKAGE_NAME + +--- + +## [1.3.1] + +- Added reverse progress + +--- + +## [1.3.0] + +- Added multiple-arc-progress feature diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..09827eb --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at . All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/README.md b/README.md index b800a38..55ff49d 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,19 @@ # CircularProgressView - + [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-CircularProgressView-brightgreen.svg?style=flat)](https://android-arsenal.com/details/1/6152) [![Preview-Appetize.io](https://img.shields.io/badge/Preview-Appetize.io-brightgreen.svg?style=flat.svg)](https://appetize.io/app/jeftbchvbfuafwpeaf00fba8bm) +![Bintray](https://img.shields.io/bintray/dt/gdelgado/android/circular-progress-view) A fancy CircularProgressView. -#### Version 1.x -- **Jul, 2019** - Added thumb size configurations support -- **Nov, 2019** - Added "gradient color" (SweepGradient) for progress -- **Apr, 2019** - Added rounded progress -- **Jun, 2018** - Added reverse progress -- **May, 2018** - Added _"multiple-arc-progress"_ -- **February, 2018** - Background alpha enable/disable -- **November, 2017** - Progress thumb and animation callback -- **September, 2017** - CircularProgressView - - ## Getting started Include it into your project, for example, as a Gradle dependency: ```groovy implementation 'com.github.guilhe:circular-progress-view:${LATEST_VERSION}' ``` - [![Maven Central](https://img.shields.io/maven-central/v/com.github.guilhe/circular-progress-view.svg)](https://search.maven.org/search?q=g:com.github.guilhe%20AND%20circular-progress-view) [![Download](https://api.bintray.com/packages/gdelgado/android/circular-progress-view/images/download.svg)](https://bintray.com/gdelgado/android/circular-progress-view/_latestVersion) - +[![Maven Central](https://img.shields.io/maven-central/v/com.github.guilhe/circular-progress-view.svg)](https://search.maven.org/search?q=g:com.github.guilhe%20AND%20circular-progress-view) [![Download](https://api.bintray.com/packages/gdelgado/android/circular-progress-view/images/download.svg)](https://bintray.com/gdelgado/android/circular-progress-view/_latestVersion) +![Bintray](https://img.shields.io/bintray/dt/gdelgado/android/circular-progress-view) ## Usage Check out the __sample__ module where you can find a few examples of how to create it by `xml` or `java`. @@ -43,8 +33,8 @@ Attributes accepted in xml: - - + + @@ -89,7 +79,7 @@ For the given array of colors: ``` The default result will be (left): - + To achieve the result on the right side you have two options: either copy the first color and add it as last, or use the helper attribute/method that does that for you: ```xml @@ -110,7 +100,7 @@ Finally, you may also use the attribute `progressBarColorArrayPositions` to pass There are many methods to help you customize this `View` by code. For more details checkout the __sample app__, _javadocs_ or the code itself. ### "Multiple Progress" for PieChart - + ```java setProgress(@NonNull List progressList, @NonNull List progressColorList) @@ -119,7 +109,7 @@ This mode can be used to display a simple pie chart. It will disable the progres ## Sample -Sample +Sample _Animation last update on April, 2019_ diff --git a/build.gradle b/build.gradle index 2909677..626a13d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,13 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + ext.kotlin_version = '1.4.0' repositories { jcenter() google() } dependencies { - classpath 'com.android.tools.build:gradle:3.6.3' + classpath 'com.android.tools.build:gradle:4.0.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -28,9 +30,9 @@ allprojects { ext { minSdkVersion = 19 - targetSdkVersion = 29 - compileSdkVersion = 29 - buildToolsVersion = '29.0.3' + targetSdkVersion = 30 + compileSdkVersion = 30 + buildToolsVersion = '30.0.1' supportLibraryVersion = '28.0.0' } \ No newline at end of file diff --git a/circular-progress-view/build.gradle b/circular-progress-view/build.gradle index aa4e8e6..0677169 100644 --- a/circular-progress-view/build.gradle +++ b/circular-progress-view/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' android { compileSdkVersion rootProject.ext.compileSdkVersion @@ -23,8 +24,12 @@ android { dependencies { implementation "com.android.support:support-annotations:$rootProject.supportLibraryVersion" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } if (project.rootProject.file('local.properties').exists()) { apply from: 'deploy.gradle' +} +repositories { + mavenCentral() } \ No newline at end of file diff --git a/circular-progress-view/src/main/java/com/github/guilhe/views/CircularProgressView.java b/circular-progress-view/src/main/java/com/github/guilhe/views/CircularProgressView.java deleted file mode 100644 index d1c0258..0000000 --- a/circular-progress-view/src/main/java/com/github/guilhe/views/CircularProgressView.java +++ /dev/null @@ -1,823 +0,0 @@ -package com.github.guilhe.views; - -import android.animation.Animator; -import android.animation.FloatEvaluator; -import android.animation.TimeInterpolator; -import android.animation.ValueAnimator; -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.RectF; -import android.graphics.Shader; -import android.graphics.SweepGradient; -import android.os.Build; -import android.support.annotation.ColorInt; -import android.support.annotation.ColorRes; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.RequiresApi; -import android.util.AttributeSet; -import android.view.View; -import android.view.animation.DecelerateInterpolator; - -import com.github.guilhe.views.circularprogress.R; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static com.github.guilhe.views.ProgressThumbScaleType.AUTO; -import static com.github.guilhe.views.ProgressThumbScaleType.POINT; -import static com.github.guilhe.views.ProgressThumbScaleType.RATE; -import static com.github.guilhe.views.ProgressThumbScaleType.values; - -@SuppressWarnings("unused") -public class CircularProgressView extends View { - - private static final String TAG = CircularProgressView.class.getSimpleName(); - - private static final float ANGLE_OFFSET_FOR_MULTIPLE_ARC_PROGRESS = 6; - private static final float DEFAULT_VIEW_PADDING_DP = 10; - private static final float DEFAULT_SHADOW_PADDING_DP = 5; - private static final float DEFAULT_STROKE_THICKNESS_DP = 10; - private static final float DEFAULT_THUMB_SIZE_DP = 10; - private static final float DEFAULT_MAXIMUM_THUMB_SIZE_RATE = 2; - private static final int DEFAULT_MAX_WIDTH_DP = 100; - private static final int DEFAULT_MAX = 100; - private static final int DEFAULT_STARTING_ANGLE = 270; - private static final int DEFAULT_ANIMATION_MILLIS = 1000; - private static final int DEFAULT_PROGRESS_COLOR = Color.BLACK; - private static final float DEFAULT_BACKGROUND_ALPHA = 0.3f; - private static final TimeInterpolator DEFAULT_INTERPOLATOR = new DecelerateInterpolator(); - - private final float mDefaultViewPadding = dpToPx(DEFAULT_VIEW_PADDING_DP); - private final float mDefaultShadowPadding = dpToPx(DEFAULT_SHADOW_PADDING_DP); - private final float mDefaultStrokeThickness = dpToPx(DEFAULT_STROKE_THICKNESS_DP); - private final float mDefaultThumbSize = dpToPx(DEFAULT_THUMB_SIZE_DP); - private final int mDefaultMaxWidth = dpToPx(DEFAULT_MAX_WIDTH_DP); - - private int mMax; - private boolean mShadowEnabled; - private boolean mProgressThumbEnabled; - private ProgressThumbScaleType mProgressThumbScaleType; - private float mMaxThumbSizeRate; - private int mStartingAngle; - private boolean mMultipleArcsEnabled; - private float mProgressListTotal; - private ArrayList mProgressList = new ArrayList<>(); - private ArrayList mProgressPaintList = new ArrayList<>(); - private float mProgress; - private float mProgressStrokeThickness; - private float mProgressThumbSize; - private float mProgressThumbSizeRate; - private float mProgressIconThickness; - private int mProgressColor; - private int mBackgroundColor; - private boolean mBackgroundAlphaEnabled; - private boolean mReverseEnabled; - private boolean mProgressRounded; - private List mValuesToDrawList = new ArrayList<>(); - - private RectF mProgressRectF; - private RectF mShadowRectF; - private Paint mBackgroundPaint; - private Paint mProgressPaint; - private Paint mThumbPaint; - private Paint mShadowPaint; - private Paint mShadowThumbPaint; - private float mLastValidRawMeasuredDim; - private float mLastValidStrokeThickness; - private float mLastValidThumbSize; - private float mLastValidThumbSizeRate; - - private TimeInterpolator mInterpolator; - private Animator mProgressAnimator; - private OnProgressChangeAnimationCallback mCallback; - private Shader mShader; - private int[] mShaderColors; - private float[] mShaderPositions; - private boolean mInitShader; - private boolean mSizeChanged = false; - - public interface OnProgressChangeAnimationCallback { - void onProgressChanged(float progress); - - void onAnimationFinished(float progress); - } - - public CircularProgressView(Context context) { - super(context); - init(context, null); - } - - public CircularProgressView(Context context, AttributeSet attrs) { - super(context, attrs); - init(context, attrs); - } - - public CircularProgressView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(context, attrs); - } - - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - public CircularProgressView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - init(context, attrs); - } - - private void init(Context context, AttributeSet attrs) { - mLastValidStrokeThickness = mDefaultStrokeThickness; - mLastValidThumbSize = mDefaultThumbSize; - mLastValidThumbSizeRate = DEFAULT_MAXIMUM_THUMB_SIZE_RATE; - mInterpolator = DEFAULT_INTERPOLATOR; - mProgressRectF = new RectF(); - mShadowRectF = new RectF(); - - mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mBackgroundPaint.setStyle(Paint.Style.STROKE); - mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mProgressPaint.setStyle(Paint.Style.STROKE); - mShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mShadowPaint.setStyle(Paint.Style.STROKE); - mShadowThumbPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mShadowThumbPaint.setStyle(Paint.Style.FILL); - mThumbPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mThumbPaint.setStyle(Paint.Style.FILL); - - if (attrs != null) { - TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CircularProgressView, 0, 0); - try { - mMax = typedArray.getInt(R.styleable.CircularProgressView_max, DEFAULT_MAX); - mShadowEnabled = typedArray.getBoolean(R.styleable.CircularProgressView_shadow, true); - mProgressThumbEnabled = typedArray.getBoolean(R.styleable.CircularProgressView_progressThumb, false); - mProgressThumbScaleType = values()[typedArray.getInteger(R.styleable.CircularProgressView_progressThumbScaleType, 0)]; - mMaxThumbSizeRate = typedArray.getFloat(R.styleable.CircularProgressView_progressThumbSizeMaxRate, DEFAULT_MAXIMUM_THUMB_SIZE_RATE); - mStartingAngle = typedArray.getInteger(R.styleable.CircularProgressView_startingAngle, DEFAULT_STARTING_ANGLE); - mProgress = typedArray.getFloat(R.styleable.CircularProgressView_progress, 0); - mProgressStrokeThickness = typedArray.getDimension(R.styleable.CircularProgressView_progressBarThickness, mDefaultStrokeThickness); - mProgressThumbSize = typedArray.getDimension(R.styleable.CircularProgressView_progressThumbSize, mDefaultThumbSize); - setProgressThumbSizeRate(typedArray.getFloat(R.styleable.CircularProgressView_progressThumbSizeRate, DEFAULT_MAXIMUM_THUMB_SIZE_RATE)); - mProgressColor = typedArray.getInt(R.styleable.CircularProgressView_progressBarColor, DEFAULT_PROGRESS_COLOR); - mProgressRounded = typedArray.getBoolean(R.styleable.CircularProgressView_progressBarRounded, false); - mBackgroundColor = typedArray.getInt(R.styleable.CircularProgressView_backgroundColor, mProgressColor); - mBackgroundAlphaEnabled = typedArray.getBoolean(R.styleable.CircularProgressView_backgroundAlphaEnabled, true); - mReverseEnabled = typedArray.getBoolean(R.styleable.CircularProgressView_reverse, false); - - int colorsId = typedArray.getResourceId(R.styleable.CircularProgressView_progressBarColorArray, -1); - boolean duplicate = typedArray.getBoolean(R.styleable.CircularProgressView_duplicateFirstColorInArray, false); - if (colorsId != -1) { - mShaderColors = typedArray.getResources().getIntArray(colorsId); - if (duplicate) { - mShaderColors = duplicateFirstColor(mShaderColors); - } - mInitShader = true; - } - int positionsId = typedArray.getResourceId(R.styleable.CircularProgressView_progressBarColorArrayPositions, -1); - if (positionsId != -1) { - TypedArray floats = typedArray.getResources().obtainTypedArray(positionsId); - mShaderPositions = new float[floats.length()]; - for (int i = 0; i < floats.length(); i++) { - mShaderPositions[i] = floats.getFloat(i, 0f); - } - floats.recycle(); - } - } finally { - typedArray.recycle(); - } - } else { - mProgressStrokeThickness = mDefaultStrokeThickness; - mProgressThumbSize = mDefaultThumbSize; - mProgressThumbSizeRate = DEFAULT_MAXIMUM_THUMB_SIZE_RATE; - mProgressThumbScaleType = AUTO; - mMaxThumbSizeRate = DEFAULT_MAXIMUM_THUMB_SIZE_RATE; - mShadowEnabled = true; - mMax = DEFAULT_MAX; - mStartingAngle = DEFAULT_STARTING_ANGLE; - mProgressColor = DEFAULT_PROGRESS_COLOR; - mBackgroundColor = mProgressColor; - mBackgroundAlphaEnabled = true; - mReverseEnabled = false; - mProgressRounded = false; - mShader = null; - mShaderColors = null; - mShaderPositions = null; - } - - resetBackgroundPaint(); - mProgressPaint.setColor(mProgressColor); - setShader(mShader); - mProgressPaint.setStrokeCap(mProgressRounded ? Paint.Cap.ROUND : Paint.Cap.SQUARE); - mShadowPaint.setColor(adjustAlpha(Color.BLACK, 0.2f)); - mShadowPaint.setStrokeCap(mProgressPaint.getStrokeCap()); - setThickness(mProgressStrokeThickness, false); - } - - /** - * Either width or height, this view will use Math.min(width, height) value. - * If an invalid size is set it won't take effect and a last valid size will be used. - * Check {@link #onMeasure(int, int)} - * - * @param size in pixels - */ - public void setSize(int size) { - getLayoutParams().height = size; - mSizeChanged = true; - requestLayout(); - } - - /** - * This method changes the progress bar starting angle. - * The default value is 270 and it's equivalent to 12 o'clock. - * - * @param angle where the progress bar starts. - */ - public void setStartingAngle(int angle) { - mStartingAngle = angle; - invalidate(); - } - - public int getStartingAngle() { - return mStartingAngle; - } - - /** - * Sets progress bar max value (100%) - * - * @param max value - */ - public void setMax(int max) { - mMax = max; - invalidate(); - } - - public int getMax() { - return mMax; - } - - /** - * Changes progress and background color - * - * @param color - Color - */ - public void setColor(int color) { - setProgressColor(color); - setBackgroundColor(color); - } - - /** - * You can simulate the use of this method with by calling {@link #setColor(int)} with ContextCompat: - * setBackgroundColor(ContextCompat.getColor(resId)); - */ - @RequiresApi(api = Build.VERSION_CODES.M) - public void setColorResource(@ColorRes int resId) { - setColor(getContext().getColor(resId)); - } - - @RequiresApi(api = Build.VERSION_CODES.O) - public void setColor(Color color) { - setColor(color.toArgb()); - } - - public void setProgressColor(int color) { - mProgressColor = color; - if (mBackgroundColor == -1) { - setBackgroundColor(color); - } - mProgressPaint.setColor(color); - setShader(null); - invalidate(); - } - - /** - * You can simulate the use of this method with by calling {@link #setProgressColor(int)} with ContextCompat: - * setProgressColor(ContextCompat.getColor(resId)); - */ - @RequiresApi(api = Build.VERSION_CODES.M) - public void setProgressColorResource(@ColorRes int resId) { - setProgressColor(getContext().getColor(resId)); - } - - @RequiresApi(api = Build.VERSION_CODES.O) - public void setProgressColor(Color color) { - setProgressColor(color.toArgb()); - } - - /** - * This will create a SweepGradient and use it as progress color. Rainboooowwwww! - * - * @param colors The colors to be distributed between around the center. - * There must be at least 2 colors in the array. - * @param positions May be NULL. The relative position of - * each corresponding color in the colors array, beginning - * with 0 and ending with 1.0. If the values are not - * monotonic, the drawing may produce unexpected results. - * If positions is NULL, then the colors are automatically - * spaced evenly. - * @param duplicateFirst to create a perfect stitch the last color from the array must be equal to the first. If true it will do it for you. - */ - public void setProgressColors(@NonNull @ColorInt int[] colors, @Nullable float[] positions, boolean duplicateFirst) { - if (duplicateFirst) { - colors = duplicateFirstColor(colors); - } - mShaderColors = colors; - mShaderPositions = positions; - setShader(new SweepGradient(mProgressRectF.centerX(), mProgressRectF.centerY(), colors, positions)); - invalidate(); - } - - public void setProgressColors(@NonNull @ColorInt int[] colors, @Nullable float[] positions) { - setProgressColors(colors, positions, false); - } - - private int[] duplicateFirstColor(@ColorInt @NonNull int[] colors) { - int[] aux = Arrays.copyOf(colors, colors.length + 1); - aux[colors.length] = colors[0]; - colors = aux; - return colors; - } - - private void setShader(Shader shader) { - mShader = shader; - mProgressPaint.setShader(shader); - } - - public int getProgressColor() { - return mProgressColor; - } - - public void setBackgroundColor(int color) { - mBackgroundColor = color; - resetBackgroundPaint(); - invalidate(); - } - - public void setBackgroundAlphaEnabled(boolean enabled) { - mBackgroundAlphaEnabled = enabled; - resetBackgroundPaint(); - invalidate(); - } - - public boolean isBackgroundAlphaEnabled() { - return mBackgroundAlphaEnabled; - } - - public void setReverseEnabled(boolean enabled) { - mReverseEnabled = enabled; - invalidate(); - } - - public boolean isReverseEnabled() { - return mReverseEnabled; - } - - public boolean isProgressRounded() { - return mProgressRounded; - } - - public void setProgressRounded(boolean enabled) { - mProgressRounded = enabled; - mProgressPaint.setStrokeCap(mProgressRounded ? Paint.Cap.ROUND : Paint.Cap.SQUARE); - mShadowPaint.setStrokeCap(mProgressPaint.getStrokeCap()); - invalidate(); - } - - /** - * You can simulate the use of this method with by calling {@link #setBackgroundColor(int)} with ContextCompat: - * setBackgroundColor(ContextCompat.getColor(resId)); - */ - @RequiresApi(api = Build.VERSION_CODES.M) - public void setShadowColorResource(@ColorRes int resId) { - setBackgroundColor(getContext().getColor(resId)); - } - - @RequiresApi(api = Build.VERSION_CODES.O) - public void setBackgroundColor(Color color) { - setBackgroundColor(color.toArgb()); - } - - public int getBackgroundColor() { - return mBackgroundColor; - } - - public void setShadowEnabled(boolean enable) { - mShadowEnabled = enable; - invalidate(); - } - - public boolean isShadowEnabled() { - return mShadowEnabled; - } - - public void setProgressThumbEnabled(boolean enable) { - mProgressThumbEnabled = enable; - invalidate(); - requestLayout(); - } - - public boolean isProgressThumbEnabled() { - return mProgressThumbEnabled; - } - - /** - * Changes progressBar & progressIcon, background and shadow line width. - * - * @param thickness in pixels - */ - public void setProgressStrokeThickness(float thickness) { - setThickness(thickness, true); - } - - private void setThickness(float thickness, boolean requestLayout) { - mProgressStrokeThickness = thickness; - mProgressIconThickness = mProgressStrokeThickness / 2; - mBackgroundPaint.setStrokeWidth(mProgressStrokeThickness); - mProgressPaint.setStrokeWidth(mProgressStrokeThickness); - if (mProgressPaintList != null) { - for (Paint paint : mProgressPaintList) { - paint.setStrokeWidth(mProgressStrokeThickness); - } - } - mShadowPaint.setStrokeWidth(mProgressStrokeThickness); - if (requestLayout) { - requestLayout(); - } - } - - public float getProgressStrokeThickness() { - return mProgressStrokeThickness; - } - - public void setProgressThumbSize(float size) { - setThumbSize(size, true); - } - - private void setThumbSize(float size, boolean requestLayout) { - mProgressThumbSize = size; - - if (requestLayout) { - requestLayout(); - } - } - - public float getProgressThumbSize() { - return mProgressThumbSize; - } - - public void setProgressThumbSizeRate(float rate) { - setThumbSizeRate(rate, true); - } - - private void setThumbSizeRate(float size, boolean requestLayout) { - mProgressThumbSizeRate = Math.max(Math.min(size, mMaxThumbSizeRate), 0); // To prevent the Thumb size too big - - if (requestLayout) { - requestLayout(); - } - } - - public float getProgressThumbSizeRate() { - return mProgressThumbSizeRate; - } - - public void setProgressMaxThumbSizeRate(float maxRate) { - mMaxThumbSizeRate = maxRate; - } - - public float getProgressMaxThumbSizeRate() { - return mMaxThumbSizeRate; - } - - public void setProgressThumbScaleType(int index) { - mProgressThumbScaleType = ProgressThumbScaleType.values()[Math.max(Math.min(index, 0), values().length-1)]; - } - - public void setProgressThumbScaleType(ProgressThumbScaleType scaleType) { - mProgressThumbScaleType = scaleType; - } - - public ProgressThumbScaleType getProgressThumbScaleType() { - return mProgressThumbScaleType; - } - - public void setProgress(float progress) { - setProgress(progress, false); - } - - public void setProgress(float progress, boolean animate) { - setProgress(progress, animate, DEFAULT_ANIMATION_MILLIS); - } - - public void setProgress(float progress, boolean animate, long duration) { - setProgress(progress, animate, duration, true); - } - - /** - * This method will activate the "multiple-arc-progress" and disable the progress thumb, progress round and background. - * This method disables the "single-arc-progress". - * - * @param progressList - list containing all the progress "step-per-arc". Their sum most be less or equal to {@link #getMax()}. - * @param progressColorList - list containing the progress "step-per-arc" color. If progressColorList.size() is less than progressList.size(), Color.TRANSPARENT will be used for the missing colors. - * @throws RuntimeException - will be thrown if progress entities sum is greater than max value. - */ - public void setProgress(@NonNull List progressList, @NonNull List progressColorList) throws RuntimeException { - setProgressRounded(false); - mProgress = mProgressListTotal = 0; - for (float value : progressList) { - mProgressListTotal += value; - if (mProgressListTotal > mMax) { - throw new RuntimeException(String.format("Progress entities sum (%s) is greater than max value (%s)", mProgressListTotal, mMax)); - } - } - - mMultipleArcsEnabled = true; - mProgressList = new ArrayList<>(progressList); - mProgressPaintList = new ArrayList<>(); - for (int i = 0; i < mProgressList.size(); i++) { - Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); - paint.setStyle(Paint.Style.STROKE); - paint.setColor(i < progressColorList.size() ? progressColorList.get(i) : Color.TRANSPARENT); - mProgressPaintList.add(paint); - } - setThickness(mProgressStrokeThickness, false); - invalidate(); - } - - public float getProgress() { - return mProgress; - } - - public void resetProgress() { - setProgress(0); - } - - public void resetProgress(boolean animate) { - resetProgress(animate, DEFAULT_ANIMATION_MILLIS); - } - - public void resetProgress(boolean animate, long duration) { - setProgress(0, animate, duration, false); - } - - public void setAnimationInterpolator(TimeInterpolator interpolator) { - mInterpolator = interpolator == null ? DEFAULT_INTERPOLATOR : interpolator; - } - - public void setProgressAnimationCallback(OnProgressChangeAnimationCallback callback) { - mCallback = callback; - } - - private void resetBackgroundPaint() { - mBackgroundPaint.setColor(mBackgroundAlphaEnabled ? adjustAlpha(mBackgroundColor, DEFAULT_BACKGROUND_ALPHA) : mBackgroundColor); - } - - /** - * This method will activate the "single-arc-progress" and enable the progress thumb and background. - * This method disables the "multiple-arc-progress". - */ - private void setProgress(float progress, boolean animate, long duration, boolean clockwise) { - mMultipleArcsEnabled = false; - if (animate) { - if (mProgressAnimator != null) { - mProgressAnimator.cancel(); - } - mProgressAnimator = getAnimator(getProgress(), clockwise ? progress : 0, duration, new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - setProgressValue(((Float) valueAnimator.getAnimatedValue())); - if (mCallback != null) { - mCallback.onProgressChanged(mProgress); - } - } - }); - mProgressAnimator.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationEnd(Animator animation) { - if (mCallback != null) { - mCallback.onAnimationFinished(mProgress); - } - } - - @Override - public void onAnimationStart(Animator animation) { - //not in use - } - - @Override - public void onAnimationCancel(Animator animation) { - //not in use - } - - @Override - public void onAnimationRepeat(Animator animation) { - //not in use - } - }); - mProgressAnimator.start(); - } else { - setProgressValue(progress); - } - } - - private void setProgressValue(float value) { - mProgress = value; - invalidate(); - } - - private ValueAnimator getAnimator(double current, double next, long duration, ValueAnimator.AnimatorUpdateListener updateListener) { - ValueAnimator animator = new ValueAnimator(); - animator.setInterpolator(mInterpolator); - animator.setDuration(duration); - animator.setObjectValues(current, next); - animator.setEvaluator(new FloatEvaluator() { - public Integer evaluate(float fraction, float startValue, float endValue) { - return Math.round(startValue + (endValue - startValue) * fraction); - } - }); - animator.addUpdateListener(updateListener); - return animator; - } - - /** - * Changes color's alpha by the factor - * - * @param color The color to change alpha - * @param factor 1.0f (solid) to 0.0f (transparent) - * @return int - A color with modified alpha - */ - private int adjustAlpha(int color, float factor) { - int alpha = Math.round(Color.alpha(color) * factor); - int red = Color.red(color); - int green = Color.green(color); - int blue = Color.blue(color); - return Color.argb(alpha, red, green, blue); - } - - @Override - protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int width = MeasureSpec.getSize(widthMeasureSpec); - int height = MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.UNSPECIFIED ? MeasureSpec.getSize(heightMeasureSpec) : mDefaultMaxWidth; - - int rawMeasuredDim = Math.max(Math.min(width, height), 0); - float progressWidth = mProgressStrokeThickness; - - float thumbSize = 0; - if (mProgressThumbScaleType == POINT) { - thumbSize = mProgressThumbSize; - } else if (mProgressThumbScaleType == RATE) { - thumbSize = (mProgressStrokeThickness / 2) * mProgressThumbSizeRate; - } else { - thumbSize = mProgressStrokeThickness; - } - - // if ThumbSize diameter is thicker than Stroke - if (mProgressThumbEnabled && mProgressThumbScaleType != AUTO) { - if (thumbSize * 2 > mProgressStrokeThickness) { - // increase progressWidth by thumbSize - progressWidth += thumbSize - mProgressStrokeThickness; - } else { - progressWidth = mProgressStrokeThickness / 2; - } - } - - float arcDim = Math.max(progressWidth, 0) + mDefaultViewPadding; - mProgressRectF.set(arcDim, arcDim, rawMeasuredDim - arcDim, rawMeasuredDim - arcDim); - - //To avoid creating a messy composition - if (mProgressRectF.width() <= (Math.max(progressWidth, thumbSize))) { - arcDim = mLastValidRawMeasuredDim; - mProgressRectF.set(arcDim, arcDim, rawMeasuredDim - arcDim, rawMeasuredDim - arcDim); - setThickness(mLastValidStrokeThickness, false); - setThumbSize(mLastValidThumbSize, false); - setThumbSizeRate(mLastValidThumbSizeRate, false); - } else { - mLastValidRawMeasuredDim = arcDim; - mLastValidStrokeThickness = mProgressStrokeThickness; - mLastValidThumbSize = mProgressThumbSize; - mLastValidThumbSizeRate = mProgressThumbSizeRate; - } - - mShadowRectF.set(mProgressRectF.left, mDefaultShadowPadding + mProgressRectF.top, mProgressRectF.right, mDefaultShadowPadding + mProgressRectF.bottom); - setMeasuredDimension(rawMeasuredDim, rawMeasuredDim); - } - - @Override - protected synchronized void onDraw(Canvas canvas) { - super.onDraw(canvas); - - //Either we are using "single-arc-progress" or "multiple-arc-progress". - mValuesToDrawList.clear(); - if (!mMultipleArcsEnabled) { - mValuesToDrawList.add(mProgress); - mProgressPaintList.clear(); - mProgressPaintList.add(mProgressPaint); - } else { - mValuesToDrawList.addAll(mProgressList); - } - - float angle; - float previousAngle = mStartingAngle; - float radius = (float) getWidth() / 2 - mDefaultViewPadding; - - float thumbSize = 0; - if (mProgressThumbScaleType == AUTO) { - thumbSize = mProgressIconThickness; - radius -= (mProgressIconThickness + mProgressStrokeThickness / 2); - } else { - boolean isThicker = false; - if (mProgressThumbScaleType == POINT) { - thumbSize = mProgressThumbSize; - isThicker = mProgressThumbSize * 2 > mProgressStrokeThickness; - } else if (mProgressThumbScaleType == RATE) { - thumbSize = (mProgressStrokeThickness / 2) * mProgressThumbSizeRate; - isThicker = mProgressThumbSizeRate > 1; - } - - if (isThicker) { - radius -= thumbSize; - } else { - radius -= mProgressStrokeThickness / 2; - } - } - double endX, endY; - - //Shadow logic - if (mShadowEnabled) { - angle = 360 * (mMultipleArcsEnabled ? mProgressListTotal : mProgress) / mMax; - if (mReverseEnabled) { - angle *= -1; - } - if (!mMultipleArcsEnabled && mProgressThumbEnabled) { - //Only in "single-arc-progress", otherwise we'll end up with N thumbs - - //Who doesn't love a bit of math? :) - //cos(a) = adj / hyp <>cos(angle) = x / radius <>x = cos(angle) * radius - //sin(a) = opp / hyp <>sin(angle) = y / radius <>y = sin(angle) * radius - //x = cos(startingAngle + progressAngle) * radius + originX(center) - //y = sin(startingAngle + progressAngle) * radius + originY(center) - endX = (Math.cos(Math.toRadians(previousAngle + angle)) * radius); - endY = (Math.sin(Math.toRadians(previousAngle + angle)) * radius); - mShadowThumbPaint.set(mShadowPaint); // shadow stroke style copy - switch(mProgressThumbScaleType) { - case POINT: - case RATE: - mShadowThumbPaint.setStyle(Paint.Style.FILL); - break; - case AUTO: - default: - mShadowThumbPaint.setStyle(Paint.Style.STROKE); - } - canvas.drawCircle((float) endX + mShadowRectF.centerX(), (float) endY + mShadowRectF.centerY(), thumbSize, mShadowThumbPaint); - } - canvas.drawArc(mShadowRectF, previousAngle, angle, false, mShadowPaint); - } - - //Progress logic - if (mInitShader) { - mInitShader = false; - setShader(new SweepGradient(mProgressRectF.centerX(), mProgressRectF.centerY(), mShaderColors, mShaderPositions)); - } else if (mSizeChanged) { - mSizeChanged = false; - setShader(new SweepGradient(mProgressRectF.centerX(), mProgressRectF.centerY(), mShaderColors, mShaderPositions)); - } - - for (int i = 0; i < mValuesToDrawList.size(); i++) { - if (!mMultipleArcsEnabled) { - //No background will be used when "multiple-arc-progress" is enable because it will be mixed with the "progress-colors" - canvas.drawOval(mProgressRectF, mBackgroundPaint); - } - - angle = 360 * mValuesToDrawList.get(i) / mMax; - if (mReverseEnabled) { - angle *= -1; - } - float offset = !mReverseEnabled && mMultipleArcsEnabled ? ANGLE_OFFSET_FOR_MULTIPLE_ARC_PROGRESS : 0; //to better glue all the "pieces" - canvas.drawArc(mProgressRectF, previousAngle - offset, angle + offset, false, mProgressPaintList.get(i)); - if (!mMultipleArcsEnabled && mProgressThumbEnabled) { - //Only in "single-arc-progress", otherwise we'll end up with N thumbs - endX = (Math.cos(Math.toRadians(previousAngle + angle)) * radius); - endY = (Math.sin(Math.toRadians(previousAngle + angle)) * radius); - - mThumbPaint.set(mProgressPaintList.get(i)); // stroke style copy - switch(mProgressThumbScaleType) { - case POINT: - case RATE: - mThumbPaint.setStyle(Paint.Style.FILL); - break; - case AUTO: - default: - mThumbPaint.setStyle(Paint.Style.STROKE); - } - canvas.drawCircle((float) endX + mProgressRectF.centerX(), (float) endY + mProgressRectF.centerY(), thumbSize, mThumbPaint); - } - previousAngle += angle; - } - } - - public int dpToPx(float dp) { - return (int) Math.ceil(dp * Resources.getSystem().getDisplayMetrics().density); - } -} \ No newline at end of file diff --git a/circular-progress-view/src/main/java/com/github/guilhe/views/CircularProgressView.kt b/circular-progress-view/src/main/java/com/github/guilhe/views/CircularProgressView.kt new file mode 100644 index 0000000..8342ecf --- /dev/null +++ b/circular-progress-view/src/main/java/com/github/guilhe/views/CircularProgressView.kt @@ -0,0 +1,619 @@ +package com.github.guilhe.views + +import android.animation.Animator +import android.animation.FloatEvaluator +import android.animation.TimeInterpolator +import android.animation.ValueAnimator +import android.animation.ValueAnimator.AnimatorUpdateListener +import android.content.Context +import android.content.res.Resources +import android.graphics.* +import android.os.Build +import android.support.annotation.ColorInt +import android.support.annotation.ColorRes +import android.support.annotation.RequiresApi +import android.util.AttributeSet +import android.view.View +import android.view.animation.DecelerateInterpolator +import com.github.guilhe.views.ProgressThumbScaleType.* +import com.github.guilhe.views.circularprogress.R +import java.util.* +import kotlin.jvm.Throws +import kotlin.math.* + +@Suppress("unused", "MemberVisibilityCanBePrivate") +class CircularProgressView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, +) : View(context, attrs, defStyleAttr) { + + private val defaultViewPadding = dpToPx(DEFAULT_VIEW_PADDING_DP).toFloat() + private val defaultShadowPadding = dpToPx(DEFAULT_SHADOW_PADDING_DP).toFloat() + private val defaultStrokeThickness = dpToPx(DEFAULT_STROKE_THICKNESS_DP).toFloat() + private val defaultThumbSize = dpToPx(DEFAULT_THUMB_SIZE_DP).toFloat() + private val defaultMaxWidth = dpToPx(DEFAULT_MAX_WIDTH_DP.toFloat()) + private val valuesToDrawList: MutableList = ArrayList() + + private lateinit var progressRectF: RectF + private lateinit var shadowRectF: RectF + private lateinit var backgroundPaint: Paint + private lateinit var progressPaint: Paint + private lateinit var shadowPaint: Paint + private lateinit var shadowThumbPaint: Paint + private lateinit var thumbPaint: Paint + private lateinit var progressAnimator: ValueAnimator + + private var multipleArcsEnabled = false + private var progressListTotal = 0f + private var progressList = ArrayList() + private var progressPaintList: ArrayList = ArrayList() + private var progress = 0f + private var progressThumbSizeRate = DEFAULT_MAXIMUM_THUMB_SIZE_RATE + private var progressIconThickness = 0f + private var progressBackgroundAlphaEnabled = true + private var reverseEnabled = false + private var progressRounded = false + private var lastValidRawMeasuredDim = 0f + private var lastValidStrokeThickness = defaultStrokeThickness + private var lastValidThumbSize = defaultThumbSize + private var lastValidThumbSizeRate = DEFAULT_MAXIMUM_THUMB_SIZE_RATE + private var interpolator: TimeInterpolator = DEFAULT_INTERPOLATOR + private var shader: Shader? = null + private var shaderColors: IntArray = intArrayOf() + private var shaderPositions: FloatArray = floatArrayOf() + private var initShader = false + private var sizeChanged = false + + var progressMaxThumbSizeRate = DEFAULT_MAXIMUM_THUMB_SIZE_RATE + var progressThumbSize = defaultThumbSize + var actionCallback: CircularProgressViewActionCallback? = null + + var isBackgroundAlphaEnabled: Boolean = false + set(enabled) { + field = enabled + resetBackgroundPaint() + invalidate() + } + + var isReverseEnabled: Boolean = false + set(enabled) { + field = enabled + invalidate() + } + + var isProgressRounded: Boolean = false + set(enabled) { + field = enabled + progressPaint.strokeCap = if (progressRounded) Paint.Cap.ROUND else Paint.Cap.SQUARE + shadowPaint.strokeCap = progressPaint.strokeCap + invalidate() + } + + var isShadowEnabled: Boolean = true + set(enable) { + field = enable + invalidate() + } + + var isProgressThumbEnabled: Boolean = false + set(enable) { + field = enable + invalidate() + requestLayout() + } + + var progressColor: Int = DEFAULT_PROGRESS_COLOR + set(color) { + field = color + if (color == -1) { + progressBackgroundColor = color + } + progressPaint.color = color + setShader(null) + invalidate() + } + + var progressBackgroundColor: Int = DEFAULT_PROGRESS_COLOR + set(color) { + field = color + resetBackgroundPaint() + invalidate() + } + + /** + * Changes progressBar & progressIcon, background and shadow line width. Thickness in pixels. + */ + var progressStrokeThickness: Float = defaultStrokeThickness + set(thickness) { + setThickness(thickness, true) + } + + var progressThumbScaleType: ProgressThumbScaleType = AUTO + set(type) { + field = values()[max(min(type.ordinal, 0), values().size - 1)] + } + + /** + * This method changes the progress bar starting angle. + * The default value is 270 and it's equivalent to 12 o'clock. + */ + var startingAngle: Int = DEFAULT_STARTING_ANGLE + set(angle) { + field = angle + invalidate() + } + + /** + * Sets progress bar max value (100%) + */ + var max: Int = DEFAULT_MAX + set(value) { + field = value + invalidate() + } + + interface CircularProgressViewActionCallback { + fun onProgressChanged(progress: Float) + fun onAnimationFinished(progress: Float) + } + + init { + setupAttr(context, attrs) + } + + private fun setupAttr(context: Context, attrs: AttributeSet?) { + progressRectF = RectF() + shadowRectF = RectF() + backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG) + backgroundPaint.style = Paint.Style.STROKE + progressPaint = Paint(Paint.ANTI_ALIAS_FLAG) + progressPaint.style = Paint.Style.STROKE + shadowPaint = Paint(Paint.ANTI_ALIAS_FLAG) + shadowPaint.style = Paint.Style.STROKE + shadowThumbPaint = Paint(Paint.ANTI_ALIAS_FLAG) + shadowThumbPaint.style = Paint.Style.FILL + thumbPaint = Paint(Paint.ANTI_ALIAS_FLAG) + thumbPaint.style = Paint.Style.FILL + + if (attrs != null) { + val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.CircularProgressView, 0, 0) + try { + max = typedArray.getInt(R.styleable.CircularProgressView_max, DEFAULT_MAX) + isShadowEnabled = typedArray.getBoolean(R.styleable.CircularProgressView_shadow, true) + isProgressThumbEnabled = typedArray.getBoolean(R.styleable.CircularProgressView_progressThumb, false) + progressThumbScaleType = values()[typedArray.getInteger(R.styleable.CircularProgressView_progressThumbScaleType, 0)] + progressMaxThumbSizeRate = typedArray.getFloat(R.styleable.CircularProgressView_progressThumbSizeMaxRate, DEFAULT_MAXIMUM_THUMB_SIZE_RATE) + startingAngle = typedArray.getInteger(R.styleable.CircularProgressView_startingAngle, DEFAULT_STARTING_ANGLE) + progress = typedArray.getFloat(R.styleable.CircularProgressView_progress, 0f) + progressStrokeThickness = typedArray.getDimension(R.styleable.CircularProgressView_progressBarThickness, defaultStrokeThickness) + progressThumbSize = typedArray.getDimension(R.styleable.CircularProgressView_progressThumbSize, defaultThumbSize) + progressThumbSizeRate = typedArray.getFloat(R.styleable.CircularProgressView_progressThumbSizeRate, DEFAULT_MAXIMUM_THUMB_SIZE_RATE) + progressColor = typedArray.getInt(R.styleable.CircularProgressView_progressBarColor, DEFAULT_PROGRESS_COLOR) + progressRounded = typedArray.getBoolean(R.styleable.CircularProgressView_progressBarRounded, false) + progressBackgroundColor = typedArray.getInt(R.styleable.CircularProgressView_progressBackgroundColor, progressColor) + progressBackgroundAlphaEnabled = typedArray.getBoolean(R.styleable.CircularProgressView_progressBackgroundAlphaEnabled, true) + reverseEnabled = typedArray.getBoolean(R.styleable.CircularProgressView_reverse, false) + val colorsId = typedArray.getResourceId(R.styleable.CircularProgressView_progressBarColorArray, -1) + val duplicate = typedArray.getBoolean(R.styleable.CircularProgressView_duplicateFirstColorInArray, false) + if (colorsId != -1) { + shaderColors = typedArray.resources.getIntArray(colorsId) + if (duplicate) { + shaderColors = duplicateFirstColor(shaderColors) + } + initShader = true + } + val positionsId = typedArray.getResourceId(R.styleable.CircularProgressView_progressBarColorArrayPositions, -1) + if (positionsId != -1) { + val floats = typedArray.resources.obtainTypedArray(positionsId) + shaderPositions = FloatArray(floats.length()) + for (i in 0 until floats.length()) { + shaderPositions[i] = floats.getFloat(i, 0f) + } + floats.recycle() + } + } finally { + typedArray.recycle() + } + } + + resetBackgroundPaint() + progressPaint.color = progressColor + setShader(shader) + progressPaint.strokeCap = if (progressRounded) Paint.Cap.ROUND else Paint.Cap.SQUARE + shadowPaint.color = adjustAlpha(Color.BLACK, 0.2f) + shadowPaint.strokeCap = progressPaint.strokeCap + setThickness(progressStrokeThickness, false) + } + + /** + * Either width or height, this view will use Math.min(width, height) value. + * If an invalid size is set it won't take effect and a last valid size will be used. + * Check [.onMeasure] + * + * @param size in pixels + */ + fun setSize(size: Int) { + layoutParams.height = size + sizeChanged = true + requestLayout() + } + + /** + * Changes progress and background color + * + * @param color - Color + */ + fun setColor(color: Int) { + progressColor = color + progressBackgroundColor = color + } + + /** + * You can simulate the use of this method with by calling [.setColor] with ContextCompat: + * setBackgroundColor(ContextCompat.getColor(resId)); + */ + @RequiresApi(api = Build.VERSION_CODES.M) + fun setColorResource(@ColorRes resId: Int) { + setColor(context.getColor(resId)) + } + + @RequiresApi(api = Build.VERSION_CODES.O) + fun setColor(color: Color) { + setColor(color.toArgb()) + } + + /** + * You can simulate the use of this method with by calling [.setProgressColor] with ContextCompat: + * setProgressColor(ContextCompat.getColor(resId)); + */ + @RequiresApi(api = Build.VERSION_CODES.M) + fun setProgressColorResource(@ColorRes resId: Int) { + progressColor = context.getColor(resId) + } + + @RequiresApi(api = Build.VERSION_CODES.O) + fun setProgressColor(color: Color) { + progressColor = color.toArgb() + } + + /** + * This will create a SweepGradient and use it as progress color. Rainboooowwwww! + * + * @param colors - The colors to be distributed between around the center. There must be at least 2 colors in the array. + * @param positions - May be NULL. The relative position of each corresponding color in the colors array, beginning with 0 and ending with 1.0. + * If the values are not monotonic, the drawing may produce unexpected results. If positions is NULL, then the colors are automatically spaced evenly. + * @param duplicateFirst to create a perfect stitch the last color from the array must be equal to the first. If true it will do it for you. + */ + @JvmOverloads + fun setProgressColors(@ColorInt colors: IntArray, positions: FloatArray, duplicateFirst: Boolean = false) { + shaderColors = if (duplicateFirst) duplicateFirstColor(colors) else colors + shaderPositions = positions + setShader(SweepGradient(progressRectF.centerX(), progressRectF.centerY(), colors, positions)) + invalidate() + } + + private fun duplicateFirstColor(@ColorInt colors: IntArray): IntArray { + return colors.copyOf(colors.size + 1).also { + it[colors.size] = colors[0] + } + } + + private fun setShader(shader: Shader?) { + progressPaint.shader = shader + } + + /** + * You can simulate the use of this method with by calling [.setBackgroundColor] with ContextCompat: + * setBackgroundColor(ContextCompat.getColor(resId)); + */ + @RequiresApi(api = Build.VERSION_CODES.M) + fun setShadowColorResource(@ColorRes resId: Int) = setBackgroundColor(context.getColor(resId)) + + @RequiresApi(api = Build.VERSION_CODES.O) + fun setBackgroundColor(color: Color) = setBackgroundColor(color.toArgb()) + + private fun setThickness(thickness: Float, requestLayout: Boolean) { + progressStrokeThickness = thickness + progressIconThickness = progressStrokeThickness / 2 + backgroundPaint.strokeWidth = progressStrokeThickness + progressPaint.strokeWidth = progressStrokeThickness + for (paint in progressPaintList) { + paint.strokeWidth = progressStrokeThickness + } + shadowPaint.strokeWidth = progressStrokeThickness + if (requestLayout) { + requestLayout() + } + } + + @JvmOverloads + fun setProgress(progress: Float, animate: Boolean = false, duration: Long = DEFAULT_ANIMATION_MILLIS.toLong()) { + setProgress(progress, animate, duration, true) + } + + /** + * This method will activate the "multiple-arc-progress" and disable the progress thumb, progress round and background. + * This method disables the "single-arc-progress". + * + * @param progressList - list containing all the progress "step-per-arc". Their sum most be less or equal to [.getMax]. + * @param progressColorList - list containing the progress "step-per-arc" color. If progressColorList.size() is less than progressList.size(), Color.TRANSPARENT will be used for the missing colors. + * @throws RuntimeException - will be thrown if progress entities sum is greater than max value. + */ + @Throws(RuntimeException::class) + fun setProgress(progressList: List, progressColorList: List) { + progressRounded = false + progressListTotal = 0f + progress = progressListTotal + for (value in progressList) { + progressListTotal += value + if (progressListTotal > max) { + throw RuntimeException(String.format("Progress entities sum (%s) is greater than max value (%s)", progressListTotal, max)) + } + } + multipleArcsEnabled = true + this.progressList = ArrayList(progressList) + progressPaintList = ArrayList() + for (i in progressList.indices) { + val paint = Paint(Paint.ANTI_ALIAS_FLAG) + paint.style = Paint.Style.STROKE + paint.color = if (i < progressColorList.size) progressColorList[i] else Color.TRANSPARENT + progressPaintList.add(paint) + } + setThickness(progressStrokeThickness, false) + invalidate() + } + + @JvmOverloads + fun resetProgress(animate: Boolean = false, duration: Long = DEFAULT_ANIMATION_MILLIS.toLong()) { + setProgress(0f, animate, duration, false) + } + + fun setAnimationInterpolator(interpolator: TimeInterpolator?) { + this.interpolator = interpolator ?: DEFAULT_INTERPOLATOR + } + + private fun resetBackgroundPaint() { + backgroundPaint.color = if (progressBackgroundAlphaEnabled) adjustAlpha(progressBackgroundColor, DEFAULT_BACKGROUND_ALPHA) else progressBackgroundColor + } + + /** + * This method will activate the "single-arc-progress" and enable the progress thumb and background. + * This method disables the "multiple-arc-progress". + */ + private fun setProgress(progress: Float, animate: Boolean, duration: Long, clockwise: Boolean) { + multipleArcsEnabled = false + if (animate) { + if (::progressAnimator.isInitialized) { + progressAnimator.cancel() + } + progressAnimator = getAnimator(progress.toDouble(), if (clockwise) progress.toDouble() else 0.toDouble(), duration, AnimatorUpdateListener { valueAnimator -> + setProgressValue(valueAnimator.animatedValue as Float) + actionCallback?.onProgressChanged(progress) + }) + progressAnimator.addListener(object : Animator.AnimatorListener { + override fun onAnimationEnd(animation: Animator) { + actionCallback?.onAnimationFinished(progress) + } + + override fun onAnimationStart(animation: Animator) {} + + override fun onAnimationCancel(animation: Animator) {} + + override fun onAnimationRepeat(animation: Animator) {} + }) + progressAnimator.start() + } else { + setProgressValue(progress) + } + } + + private fun setProgressValue(value: Float) { + progress = value + invalidate() + } + + private fun getAnimator(current: Double, next: Double, duration: Long, updateListener: AnimatorUpdateListener) = ValueAnimator().apply { + this.interpolator = interpolator + this.duration = duration + this.setObjectValues(current, next) + this.setEvaluator(object : FloatEvaluator() { + fun evaluate(fraction: Float, startValue: Float, endValue: Float): Int { + return (startValue + (endValue - startValue) * fraction).roundToInt() + } + }) + this.addUpdateListener(updateListener) + } + + /** + * Changes color's alpha by the factor + * + * @param color The color to change alpha + * @param factor 1.0f (solid) to 0.0f (transparent) + * @return int - A color with modified alpha + */ + private fun adjustAlpha(color: Int, factor: Float): Int { + val alpha = (Color.alpha(color) * factor).roundToInt() + val red = Color.red(color) + val green = Color.green(color) + val blue = Color.blue(color) + return Color.argb(alpha, red, green, blue) + } + + @Synchronized + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + val width = MeasureSpec.getSize(widthMeasureSpec) + val height = if (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.UNSPECIFIED) MeasureSpec.getSize(heightMeasureSpec) else defaultMaxWidth + val rawMeasuredDim = max(min(width, height), 0) + var progressWidth = progressStrokeThickness + val thumbSize = when (progressThumbScaleType) { + POINT -> progressThumbSize + RATE -> progressStrokeThickness / 2 * progressThumbSizeRate + else -> progressStrokeThickness + } + + // if ThumbSize diameter is thicker than Stroke + if (isProgressThumbEnabled && progressThumbScaleType != AUTO) { + if (thumbSize * 2 > progressStrokeThickness) { + // increase progressWidth by thumbSize + progressWidth += thumbSize - progressStrokeThickness + } else { + progressWidth = progressStrokeThickness / 2 + } + } + var arcDim = max(progressWidth, 0f) + defaultViewPadding + progressRectF[arcDim, arcDim, rawMeasuredDim - arcDim] = rawMeasuredDim - arcDim + + //To avoid creating a messy composition + if (progressRectF.width() <= max(progressWidth, thumbSize)) { + arcDim = lastValidRawMeasuredDim + progressRectF[arcDim, arcDim, rawMeasuredDim - arcDim] = rawMeasuredDim - arcDim + setThickness(lastValidStrokeThickness, false) + progressThumbSize = lastValidThumbSize + progressThumbSizeRate = max(min(lastValidThumbSizeRate, progressMaxThumbSizeRate), 0f) // To prevent the Thumb size too big + } else { + lastValidRawMeasuredDim = arcDim + lastValidStrokeThickness = progressStrokeThickness + lastValidThumbSize = progressThumbSize + lastValidThumbSizeRate = progressThumbSizeRate + } + shadowRectF[progressRectF.left, defaultShadowPadding + progressRectF.top, progressRectF.right] = defaultShadowPadding + progressRectF.bottom + setMeasuredDimension(rawMeasuredDim, rawMeasuredDim) + } + + @Synchronized + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + //Either we are using "single-arc-progress" or "multiple-arc-progress". + valuesToDrawList.clear() + if (!multipleArcsEnabled) { + valuesToDrawList.add(progress) + progressPaintList.clear() + progressPaintList.add(progressPaint) + } else { + valuesToDrawList.addAll(progressList) + } + var angle: Float + var previousAngle = startingAngle.toFloat() + var radius = width.toFloat() / 2 - defaultViewPadding + var thumbSize = 0f + if (progressThumbScaleType == AUTO) { + thumbSize = progressIconThickness + radius -= progressIconThickness + progressStrokeThickness / 2 + } else { + var isThicker = false + if (progressThumbScaleType == POINT) { + thumbSize = progressThumbSize + isThicker = progressThumbSize * 2 > progressStrokeThickness + } else if (progressThumbScaleType == RATE) { + thumbSize = progressStrokeThickness / 2 * progressThumbSizeRate + isThicker = progressThumbSizeRate > 1 + } + radius -= if (isThicker) { + thumbSize + } else { + progressStrokeThickness / 2 + } + } + var endX: Double + var endY: Double + + //Shadow logic + if (isShadowEnabled) { + angle = 360 * (if (multipleArcsEnabled) progressListTotal else progress) / max + if (reverseEnabled) { + angle *= -1f + } + if (!multipleArcsEnabled && isProgressThumbEnabled) { + //Only in "single-arc-progress", otherwise we'll end up with N thumbs + + //Who doesn't love a bit of math? :) + //cos(a) = adj / hyp <>cos(angle) = x / radius <>x = cos(angle) * radius + //sin(a) = opp / hyp <>sin(angle) = y / radius <>y = sin(angle) * radius + //x = cos(startingAngle + progressAngle) * radius + originX(center) + //y = sin(startingAngle + progressAngle) * radius + originY(center) + endX = cos(Math.toRadians(previousAngle + angle.toDouble())) * radius + endY = sin(Math.toRadians(previousAngle + angle.toDouble())) * radius + shadowThumbPaint.set(shadowPaint) // shadow stroke style copy + when (progressThumbScaleType) { + POINT, RATE -> shadowThumbPaint.style = Paint.Style.FILL + else -> shadowThumbPaint.style = Paint.Style.STROKE + } + canvas.drawCircle(endX.toFloat() + shadowRectF.centerX(), endY.toFloat() + shadowRectF.centerY(), thumbSize, shadowThumbPaint) + } + canvas.drawArc(shadowRectF, previousAngle, angle, false, shadowPaint) + } + + //Progress logic + if (initShader) { + initShader = false + setShader(SweepGradient(progressRectF.centerX(), progressRectF.centerY(), shaderColors, shaderPositions)) + } else if (sizeChanged) { + sizeChanged = false + setShader(SweepGradient(progressRectF.centerX(), progressRectF.centerY(), shaderColors, shaderPositions)) + } + for (i in valuesToDrawList.indices) { + if (!multipleArcsEnabled) { + //No background will be used when "multiple-arc-progress" is enable because it will be mixed with the "progress-colors" + canvas.drawOval(progressRectF, backgroundPaint) + } + angle = 360 * valuesToDrawList[i] / max + if (reverseEnabled) { + angle *= -1f + } + val offset: Float = if (!reverseEnabled && multipleArcsEnabled) ANGLE_OFFSET_FOR_MULTIPLE_ARC_PROGRESS else 0f //to better glue all the "pieces" + canvas.drawArc(progressRectF, previousAngle - offset, angle + offset, false, progressPaintList[i]) + if (!multipleArcsEnabled && isProgressThumbEnabled) { + //Only in "single-arc-progress", otherwise we'll end up with N thumbs + endX = cos(Math.toRadians(previousAngle + angle.toDouble())) * radius + endY = sin(Math.toRadians(previousAngle + angle.toDouble())) * radius + thumbPaint.set(progressPaintList[i]) // stroke style copy + when (progressThumbScaleType) { + POINT, RATE -> thumbPaint.style = Paint.Style.FILL + else -> thumbPaint.style = Paint.Style.STROKE + } + canvas.drawCircle(endX.toFloat() + progressRectF.centerX(), endY.toFloat() + progressRectF.centerY(), thumbSize, thumbPaint) + } + previousAngle += angle + } + } + + private fun dpToPx(dp: Float) = ceil(dp * Resources.getSystem().displayMetrics.density.toDouble()).toInt() + + companion object { + private const val ANGLE_OFFSET_FOR_MULTIPLE_ARC_PROGRESS = 6f + private const val DEFAULT_VIEW_PADDING_DP = 10f + private const val DEFAULT_SHADOW_PADDING_DP = 5f + private const val DEFAULT_STROKE_THICKNESS_DP = 10f + private const val DEFAULT_THUMB_SIZE_DP = 10f + private const val DEFAULT_MAXIMUM_THUMB_SIZE_RATE = 2f + private const val DEFAULT_MAX_WIDTH_DP = 100 + private const val DEFAULT_MAX = 100 + private const val DEFAULT_STARTING_ANGLE = 270 + private const val DEFAULT_ANIMATION_MILLIS = 1000 + private const val DEFAULT_PROGRESS_COLOR = Color.BLACK + private const val DEFAULT_BACKGROUND_ALPHA = 0.3f + private val DEFAULT_INTERPOLATOR: TimeInterpolator = DecelerateInterpolator() + } +} + +enum class ProgressThumbScaleType { AUTO, POINT, RATE } + +@Suppress("unused") +inline fun CircularProgressView.addActionListener( + crossinline onProgressChanged: (progress: Float) -> Unit = { _ -> }, + crossinline onAnimationFinished: (progress: Float) -> Unit = { _ -> }, +): CircularProgressView.CircularProgressViewActionCallback { + val callback = object : CircularProgressView.CircularProgressViewActionCallback { + override fun onProgressChanged(progress: Float) { + onProgressChanged.invoke(progress) + } + + override fun onAnimationFinished(progress: Float) { + onAnimationFinished.invoke(progress) + } + } + actionCallback = callback + return callback +} \ No newline at end of file diff --git a/circular-progress-view/src/main/java/com/github/guilhe/views/ProgressThumbScaleType.java b/circular-progress-view/src/main/java/com/github/guilhe/views/ProgressThumbScaleType.java deleted file mode 100644 index 9e44812..0000000 --- a/circular-progress-view/src/main/java/com/github/guilhe/views/ProgressThumbScaleType.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.github.guilhe.views; - -public enum ProgressThumbScaleType { - AUTO, POINT, RATE -} diff --git a/circular-progress-view/src/main/res/values/attrs.xml b/circular-progress-view/src/main/res/values/attrs.xml index abb07fd..34399df 100644 --- a/circular-progress-view/src/main/res/values/attrs.xml +++ b/circular-progress-view/src/main/res/values/attrs.xml @@ -14,8 +14,8 @@ - - + + @@ -39,8 +39,8 @@ - - + + \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index b03cfb1..ddca840 100644 --- a/gradle.properties +++ b/gradle.properties @@ -23,7 +23,7 @@ bintrayName = circular-progress-view libraryName = CircularProgressView libraryDescription = A fancy CircularProgressView -libraryVersion = 1.4.2 +libraryVersion = 2.0.0 siteUrl = https://github.com/GuilhE/android-circular-progress-view gitUrl = https://github.com/GuilhE/android-circular-progress-view.git diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c56a31a..11f57fd 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Apr 24 22:10:51 WEST 2020 +#Mon Aug 24 13:26:38 WEST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip diff --git a/.imgs/banner.png b/media/banner.png similarity index 100% rename from .imgs/banner.png rename to media/banner.png diff --git a/.imgs/piechart.png b/media/piechart.png similarity index 100% rename from .imgs/piechart.png rename to media/piechart.png diff --git a/.imgs/rainbow.png b/media/rainbow.png similarity index 100% rename from .imgs/rainbow.png rename to media/rainbow.png diff --git a/.imgs/sample.gif b/media/sample.gif similarity index 100% rename from .imgs/sample.gif rename to media/sample.gif diff --git a/sample/build.gradle b/sample/build.gradle index 1ca3581..876bd5e 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -1,4 +1,6 @@ apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' android { compileSdkVersion rootProject.ext.compileSdkVersion @@ -8,8 +10,8 @@ android { applicationId "com.github.guilhe.cicularprogressview.sample" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 7 - versionName "1.4.1" + versionCode 8 + versionName "2.0.0" } dataBinding { @@ -37,5 +39,9 @@ dependencies { implementation project(':circular-progress-view') implementation "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion" implementation "com.android.support:design:$rootProject.supportLibraryVersion" - implementation 'com.android.support.constraint:constraint-layout:1.1.3' + implementation 'com.android.support.constraint:constraint-layout:2.0.0' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} +repositories { + mavenCentral() } \ No newline at end of file diff --git a/sample/src/main/java/sample/SampleActivity.java b/sample/src/main/java/sample/SampleActivity.java index 6ac876d..9ae8640 100644 --- a/sample/src/main/java/sample/SampleActivity.java +++ b/sample/src/main/java/sample/SampleActivity.java @@ -18,76 +18,72 @@ import java.util.List; import java.util.Random; -/** - * Created by gdelgado on 30/08/2017. - */ - public class SampleActivity extends AppCompatActivity implements SeekBar.OnSeekBarChangeListener, RadioGroup.OnCheckedChangeListener { - private ActivitySampleEditorBinding mBinding; - private boolean mTransparent; - private Toast mToast; + private ActivitySampleEditorBinding binding; + private boolean transparent; + private Toast toast; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mBinding = DataBindingUtil.setContentView(this, R.layout.activity_sample_editor); - - mBinding.sizeSeekBar.setOnSeekBarChangeListener(this); - mBinding.thicknessSeekBar.setOnSeekBarChangeListener(this); - mBinding.thumbsizeSeekBar.setOnSeekBarChangeListener(this); - mBinding.progressSeekBar.setOnSeekBarChangeListener(this); - mBinding.angleSeekBar.setOnSeekBarChangeListener(this); - mBinding.colorRSeekBar.setOnSeekBarChangeListener(this); - mBinding.colorGSeekBar.setOnSeekBarChangeListener(this); - mBinding.colorBSeekBar.setOnSeekBarChangeListener(this); - mBinding.bgRSeekBar.setOnSeekBarChangeListener(this); - mBinding.bgGSeekBar.setOnSeekBarChangeListener(this); - mBinding.bgBSeekBar.setOnSeekBarChangeListener(this); - mBinding.thumbScaleGroup.setOnCheckedChangeListener(this); - - mBinding.roundedSwitch.setOnCheckedChangeListener((compoundButton, checked) -> mBinding.sampleCircularProgressView.setProgressRounded(checked)); - mBinding.shadowSwitch.setOnCheckedChangeListener((compoundButton, checked) -> mBinding.sampleCircularProgressView.setShadowEnabled(checked)); - mBinding.thumbSwitch.setOnCheckedChangeListener((compoundButton, checked) -> { - mBinding.sampleCircularProgressView.setProgressThumbEnabled(checked); - mBinding.thumbScaleAuto.setEnabled(checked); - mBinding.thumbScalePoint.setEnabled(checked); - mBinding.thumbScaleRate.setEnabled(checked); + binding = DataBindingUtil.setContentView(this, R.layout.activity_sample_editor); + + binding.sizeSeekBar.setOnSeekBarChangeListener(this); + binding.thicknessSeekBar.setOnSeekBarChangeListener(this); + binding.thumbsizeSeekBar.setOnSeekBarChangeListener(this); + binding.progressSeekBar.setOnSeekBarChangeListener(this); + binding.angleSeekBar.setOnSeekBarChangeListener(this); + binding.colorRSeekBar.setOnSeekBarChangeListener(this); + binding.colorGSeekBar.setOnSeekBarChangeListener(this); + binding.colorBSeekBar.setOnSeekBarChangeListener(this); + binding.bgRSeekBar.setOnSeekBarChangeListener(this); + binding.bgGSeekBar.setOnSeekBarChangeListener(this); + binding.bgBSeekBar.setOnSeekBarChangeListener(this); + binding.thumbScaleGroup.setOnCheckedChangeListener(this); + + binding.roundedSwitch.setOnCheckedChangeListener((compoundButton, checked) -> binding.sampleCircularProgressView.setProgressRounded(checked)); + binding.shadowSwitch.setOnCheckedChangeListener((compoundButton, checked) -> binding.sampleCircularProgressView.setShadowEnabled(checked)); + binding.thumbSwitch.setOnCheckedChangeListener((compoundButton, checked) -> { + binding.sampleCircularProgressView.setProgressThumbEnabled(checked); + binding.thumbScaleAuto.setEnabled(checked); + binding.thumbScalePoint.setEnabled(checked); + binding.thumbScaleRate.setEnabled(checked); }); - mBinding.reverseSwitch.setOnCheckedChangeListener((compoundButton, checked) -> mBinding.sampleCircularProgressView.setReverseEnabled(checked)); - mBinding.alphaSwitch.setOnCheckedChangeListener(((compoundButton, checked) -> mBinding.sampleCircularProgressView.setBackgroundAlphaEnabled(checked))); - mBinding.colorsSwitch.setOnCheckedChangeListener((compoundButton, checked) -> { - if (!mTransparent) { - mBinding.sampleCircularProgressView.setBackgroundColor(mBinding.sampleCircularProgressView.getProgressColor()); + binding.reverseSwitch.setOnCheckedChangeListener((compoundButton, checked) -> binding.sampleCircularProgressView.setReverseEnabled(checked)); + binding.alphaSwitch.setOnCheckedChangeListener(((compoundButton, checked) -> binding.sampleCircularProgressView.setBackgroundAlphaEnabled(checked))); + binding.colorsSwitch.setOnCheckedChangeListener((compoundButton, checked) -> { + if (!transparent) { + binding.sampleCircularProgressView.setProgressBackgroundColor(binding.sampleCircularProgressView.getProgressColor()); } }); - mBinding.transparentSwitch.setOnCheckedChangeListener((compoundButton, checked) -> { - mTransparent = checked; - mBinding.sampleCircularProgressView.setBackgroundColor(Color.TRANSPARENT); + binding.transparentSwitch.setOnCheckedChangeListener((compoundButton, checked) -> { + transparent = checked; + binding.sampleCircularProgressView.setProgressBackgroundColor(Color.TRANSPARENT); }); - mBinding.sampleCircularProgressView.setProgressAnimationCallback(new CircularProgressView.OnProgressChangeAnimationCallback() { + binding.sampleCircularProgressView.setActionCallback(new CircularProgressView.CircularProgressViewActionCallback() { @Override public void onProgressChanged(float progress) { } @Override public void onAnimationFinished(float progress) { - if (mToast != null) { - mToast.cancel(); //Prevent toasts from overlapping. + if (toast != null) { + toast.cancel(); //Prevent toasts from overlapping. } - mToast = Toast.makeText(SampleActivity.this, String.valueOf(progress) + "%", Toast.LENGTH_SHORT); - mToast.show(); + toast = Toast.makeText(SampleActivity.this, progress + "%", Toast.LENGTH_SHORT); + toast.show(); } }); - mBinding.sampleFloatingActionButton.setOnClickListener(v -> { + binding.sampleFloatingActionButton.setOnClickListener(v -> { List values = new ArrayList() {{ add(12.5f); // add(12.5f); add(25f); add(50f); }}; - mBinding.sampleCircularProgressView.setProgress(values, new ArrayList() {{ + binding.sampleCircularProgressView.setProgress(values, new ArrayList() {{ for (Float ignored : values) { add(Color.rgb(new Random().nextInt(), new Random().nextInt(), new Random().nextInt())); } @@ -99,68 +95,68 @@ public void onAnimationFinished(float progress) { public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { switch (seekBar.getId()) { case R.id.size_SeekBar: - mBinding.sampleCircularProgressView.setSize((int) (progress * getResources().getDisplayMetrics().density + 0.5f)); + binding.sampleCircularProgressView.setSize((int) (progress * getResources().getDisplayMetrics().density + 0.5f)); break; case R.id.thickness_SeekBar: - mBinding.sampleCircularProgressView.setProgressStrokeThickness((int) (progress * getResources().getDisplayMetrics().density + 0.5f)); + binding.sampleCircularProgressView.setProgressStrokeThickness((int) (progress * getResources().getDisplayMetrics().density + 0.5f)); break; case R.id.thumbsize_SeekBar: float size; - if (mBinding.sampleCircularProgressView.getProgressThumbScaleType() == ProgressThumbScaleType.RATE) { + if (binding.sampleCircularProgressView.getProgressThumbScaleType() == ProgressThumbScaleType.RATE) { size = progress / ((float) seekBar.getMax() / 2); - mBinding.sampleCircularProgressView.setProgressThumbSizeRate(size); + binding.sampleCircularProgressView.setProgressMaxThumbSizeRate(size); } else { size = (progress * getResources().getDisplayMetrics().density + 0.5f); - mBinding.sampleCircularProgressView.setProgressThumbSize(size); + binding.sampleCircularProgressView.setProgressThumbSize(size); } break; case R.id.progress_SeekBar: - mBinding.sampleCircularProgressView.setProgress(progress, mBinding.animatedSwitch.isChecked()); + binding.sampleCircularProgressView.setProgress(progress, binding.animatedSwitch.isChecked()); break; case R.id.angle_SeekBar: - mBinding.sampleCircularProgressView.setStartingAngle(progress); + binding.sampleCircularProgressView.setStartingAngle(progress); break; case R.id.color_r_SeekBar: - if (mBinding.colorsSwitch.isChecked()) { - mBinding.bgRSeekBar.setProgress(progress); + if (binding.colorsSwitch.isChecked()) { + binding.bgRSeekBar.setProgress(progress); } - mBinding.sampleCircularProgressView.setProgressColor(Color.rgb(progress, mBinding.colorGSeekBar.getProgress(), mBinding.colorBSeekBar.getProgress())); + binding.sampleCircularProgressView.setProgressColor(Color.rgb(progress, binding.colorGSeekBar.getProgress(), binding.colorBSeekBar.getProgress())); break; case R.id.color_g_SeekBar: - if (mBinding.colorsSwitch.isChecked()) { - mBinding.bgGSeekBar.setProgress(progress); + if (binding.colorsSwitch.isChecked()) { + binding.bgGSeekBar.setProgress(progress); } - mBinding.sampleCircularProgressView.setProgressColor(Color.rgb(mBinding.colorRSeekBar.getProgress(), progress, mBinding.colorBSeekBar.getProgress())); + binding.sampleCircularProgressView.setProgressColor(Color.rgb(binding.colorRSeekBar.getProgress(), progress, binding.colorBSeekBar.getProgress())); break; case R.id.color_b_SeekBar: - if (mBinding.colorsSwitch.isChecked()) { - mBinding.bgBSeekBar.setProgress(progress); + if (binding.colorsSwitch.isChecked()) { + binding.bgBSeekBar.setProgress(progress); } - mBinding.sampleCircularProgressView.setProgressColor(Color.rgb(mBinding.colorRSeekBar.getProgress(), mBinding.colorGSeekBar.getProgress(), progress)); + binding.sampleCircularProgressView.setProgressColor(Color.rgb(binding.colorRSeekBar.getProgress(), binding.colorGSeekBar.getProgress(), progress)); break; case R.id.bg_r_SeekBar: - if (mBinding.colorsSwitch.isChecked()) { - mBinding.colorRSeekBar.setProgress(progress); + if (binding.colorsSwitch.isChecked()) { + binding.colorRSeekBar.setProgress(progress); } - if (!mTransparent) { - mBinding.sampleCircularProgressView.setBackgroundColor(Color.rgb(progress, mBinding.bgGSeekBar.getProgress(), mBinding.bgBSeekBar.getProgress())); + if (!transparent) { + binding.sampleCircularProgressView.setProgressBackgroundColor(Color.rgb(progress, binding.bgGSeekBar.getProgress(), binding.bgBSeekBar.getProgress())); } break; case R.id.bg_g_SeekBar: - if (mBinding.colorsSwitch.isChecked()) { - mBinding.colorGSeekBar.setProgress(progress); + if (binding.colorsSwitch.isChecked()) { + binding.colorGSeekBar.setProgress(progress); } - if (!mTransparent) { - mBinding.sampleCircularProgressView.setBackgroundColor(Color.rgb(mBinding.bgRSeekBar.getProgress(), progress, mBinding.bgBSeekBar.getProgress())); + if (!transparent) { + binding.sampleCircularProgressView.setProgressBackgroundColor(Color.rgb(binding.bgRSeekBar.getProgress(), progress, binding.bgBSeekBar.getProgress())); } break; case R.id.bg_b_SeekBar: - if (mBinding.colorsSwitch.isChecked()) { - mBinding.colorBSeekBar.setProgress(progress); + if (binding.colorsSwitch.isChecked()) { + binding.colorBSeekBar.setProgress(progress); } - if (!mTransparent) { - mBinding.sampleCircularProgressView.setBackgroundColor(Color.rgb(mBinding.bgRSeekBar.getProgress(), mBinding.bgGSeekBar.getProgress(), progress)); + if (!transparent) { + binding.sampleCircularProgressView.setProgressBackgroundColor(Color.rgb(binding.bgRSeekBar.getProgress(), binding.bgGSeekBar.getProgress(), progress)); } break; } @@ -178,18 +174,18 @@ public void onStopTrackingTouch(SeekBar seekBar) { public void onCheckedChanged(RadioGroup group, int checkedId) { switch (checkedId) { case R.id.thumb_scale_point: - mBinding.sampleCircularProgressView.setProgressThumbScaleType(ProgressThumbScaleType.POINT); - mBinding.sampleCircularProgressView.setProgressThumbSize((mBinding.thumbsizeSeekBar.getProgress() * getResources().getDisplayMetrics().density + 0.5f)); + binding.sampleCircularProgressView.setProgressThumbScaleType(ProgressThumbScaleType.POINT); + binding.sampleCircularProgressView.setProgressThumbSize((binding.thumbsizeSeekBar.getProgress() * getResources().getDisplayMetrics().density + 0.5f)); break; case R.id.thumb_scale_rate: - mBinding.sampleCircularProgressView.setProgressThumbScaleType(ProgressThumbScaleType.RATE); - float rate = (mBinding.thumbsizeSeekBar.getProgress() / (mBinding.thumbsizeSeekBar.getMax() / mBinding.sampleCircularProgressView.getProgressMaxThumbSizeRate())); - mBinding.sampleCircularProgressView.setProgressThumbSizeRate(rate); + binding.sampleCircularProgressView.setProgressThumbScaleType(ProgressThumbScaleType.RATE); + float rate = (binding.thumbsizeSeekBar.getProgress() / (binding.thumbsizeSeekBar.getMax() / binding.sampleCircularProgressView.getProgressMaxThumbSizeRate())); + binding.sampleCircularProgressView.setProgressMaxThumbSizeRate(rate); break; case R.id.thumb_scale_auto: default: - mBinding.sampleCircularProgressView.setProgressThumbScaleType(ProgressThumbScaleType.AUTO); + binding.sampleCircularProgressView.setProgressThumbScaleType(ProgressThumbScaleType.AUTO); } - mBinding.sampleCircularProgressView.requestLayout(); + binding.sampleCircularProgressView.requestLayout(); } } \ No newline at end of file diff --git a/sample/src/main/res/layout/activity_sample_grid.xml b/sample/src/main/res/layout/activity_sample_grid.xml index 4c03204..fd55b06 100644 --- a/sample/src/main/res/layout/activity_sample_grid.xml +++ b/sample/src/main/res/layout/activity_sample_grid.xml @@ -179,7 +179,7 @@ android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="10dp" - app:backgroundColor="@android:color/transparent" + app:progressBackgroundColor="@android:color/transparent" app:layout_constraintEnd_toStartOf="@+id/circularProgressView15" app:layout_constraintStart_toEndOf="@+id/circularProgressView13" app:layout_constraintTop_toBottomOf="@+id/circularProgressView11" @@ -193,7 +193,7 @@ android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="10dp" - app:backgroundColor="@android:color/transparent" + app:progressBackgroundColor="@android:color/transparent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/circularProgressView14" app:layout_constraintTop_toBottomOf="@+id/circularProgressView12"