├── settings.gradle
├── screenshots
└── SPB_sample.gif
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── sample
├── src
│ └── main
│ │ ├── res
│ │ ├── drawable-hdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-mdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-xhdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-xxhdpi
│ │ │ └── ic_launcher.png
│ │ ├── values-v21
│ │ │ └── styles.xml
│ │ ├── anim
│ │ │ └── pocket_interpolator.xml
│ │ ├── values
│ │ │ ├── strings.xml
│ │ │ ├── colors.xml
│ │ │ └── styles.xml
│ │ └── layout
│ │ │ ├── activity_main.xml
│ │ │ └── activity_custom.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── fr
│ │ └── castorflex
│ │ └── android
│ │ └── smoothprogressbar
│ │ └── sample
│ │ ├── LookupTableInterpolator.java
│ │ ├── MainActivity.java
│ │ ├── FastOutSlowInInterpolator.java
│ │ └── MakeCustomActivity.java
└── build.gradle
├── library
├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── res
│ │ ├── values-v11
│ │ │ └── styles.xml
│ │ ├── values-v21
│ │ │ └── styles.xml
│ │ └── values
│ │ │ ├── defaults.xml
│ │ │ ├── styles.xml
│ │ │ ├── about_libraries_def.xml
│ │ │ └── attrs.xml
│ │ └── java
│ │ └── fr
│ │ └── castorflex
│ │ └── android
│ │ └── smoothprogressbar
│ │ ├── ColorsShape.java
│ │ ├── SmoothProgressBarUtils.java
│ │ ├── ContentLoadingSmoothProgressBar.java
│ │ ├── SmoothProgressBar.java
│ │ └── SmoothProgressDrawable.java
├── gradle.properties
└── build.gradle
├── library-circular
├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── res
│ │ ├── values-v21
│ │ │ └── styles.xml
│ │ └── values
│ │ │ ├── defaults.xml
│ │ │ ├── attrs.xml
│ │ │ └── styles.xml
│ │ └── java
│ │ └── fr.castorflex.android.circularprogressbar
│ │ ├── PBDelegate.java
│ │ ├── SimpleAnimatorListener.java
│ │ ├── Options.java
│ │ ├── LookupTableInterpolator.java
│ │ ├── PowerSaveModeDelegate.java
│ │ ├── FastOutSlowInInterpolator.java
│ │ ├── Utils.java
│ │ ├── CircularProgressBar.java
│ │ ├── CircularProgressDrawable.java
│ │ └── DefaultDelegate.java
├── gradle.properties
└── build.gradle
├── .travis.yml
├── .gitignore
├── gradle.properties
├── CHANGELOG.md
├── gradlew.bat
├── gradlew
├── README.md
└── LICENSE.md
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':sample'
2 | include ':library'
3 | include ':library-circular'
4 |
--------------------------------------------------------------------------------
/screenshots/SPB_sample.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/castorflex/SmoothProgressBar/HEAD/screenshots/SPB_sample.gif
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/castorflex/SmoothProgressBar/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/castorflex/SmoothProgressBar/HEAD/sample/src/main/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/castorflex/SmoothProgressBar/HEAD/sample/src/main/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/castorflex/SmoothProgressBar/HEAD/sample/src/main/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/castorflex/SmoothProgressBar/HEAD/sample/src/main/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/library-circular/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/library-circular/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
--------------------------------------------------------------------------------
/library/src/main/res/values-v11/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
--------------------------------------------------------------------------------
/library/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
--------------------------------------------------------------------------------
/sample/src/main/res/anim/pocket_interpolator.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/library/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=SmoothProgressBar Library
2 | POM_ARTIFACT_ID=library
3 | POM_PACKAGING=aar
4 | POM_DESCRIPTION=Android Library make smooth horizontal indeterminate progress bars
5 |
6 | VERSION_NAME=1.2.0-SNAPSHOT
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 |
3 | android:
4 | components:
5 | - platform-tools
6 | - tools
7 | - build-tools-26.0.0
8 | - android-25
9 |
10 | sudo: false
11 |
12 |
13 | script: ./gradlew build
14 |
--------------------------------------------------------------------------------
/library-circular/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=CircularProgressBar Library
2 | POM_ARTIFACT_ID=library-circular
3 | POM_PACKAGING=aar
4 | POM_DESCRIPTION=Android Library to make smooth circular indeterminate progress bars
5 |
6 | VERSION_NAME=1.3.0
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Aug 27 15:10:58 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
7 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | SmoothProgressBar
5 |
6 |
7 | - Accelerate
8 | - Linear
9 | - AccelerateDecelerate
10 | - Decelerate
11 | - FastOutSlowIn
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/library-circular/src/main/res/values/defaults.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #33b5e5
4 | 4dp
5 | 20
6 | 300
7 | - 1
8 | - 1
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Eclipse
2 | .project
3 | .classpath
4 | .settings
5 | .checkstyle
6 |
7 | # IntelliJ IDEA
8 | .idea
9 | *.iml
10 | *.ipr
11 | *.iws
12 | classes
13 | gen-external-apklibs
14 |
15 | # Gradle
16 | .gradle
17 | build
18 |
19 | # Maven
20 | target
21 | release.properties
22 | pom.xml.*
23 |
24 | # Ant
25 | bin
26 | gen
27 | build.xml
28 | ant.properties
29 | local.properties
30 | proguard-project.txt
31 |
32 | #Crashlytics
33 | .crashlytics_data
34 | com_crashlytics_export_strings.xml
35 |
36 | # Other
37 | out/
38 | .DS_Store
39 | tmp
40 |
--------------------------------------------------------------------------------
/library-circular/src/main/java/fr.castorflex.android.circularprogressbar/PBDelegate.java:
--------------------------------------------------------------------------------
1 | package fr.castorflex.android.circularprogressbar;
2 |
3 | import android.graphics.Canvas;
4 | import android.graphics.Paint;
5 | import android.graphics.RectF;
6 | import android.support.annotation.UiThread;
7 |
8 | interface PBDelegate {
9 |
10 | @UiThread
11 | void draw(Canvas canvas, Paint paint);
12 |
13 | @UiThread
14 | void start();
15 |
16 | @UiThread
17 | void stop();
18 |
19 | @UiThread
20 | void progressiveStop(CircularProgressDrawable.OnEndListener listener);
21 | }
22 |
--------------------------------------------------------------------------------
/library-circular/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion Integer.parseInt(project.COMPILE_SDK_VERSION)
5 | buildToolsVersion project.BUILD_TOOLS_VERSION
6 |
7 | defaultConfig {
8 | minSdkVersion 7
9 | targetSdkVersion 26
10 | versionName project.VERSION_NAME
11 | versionCode Integer.parseInt(project.VERSION_CODE)
12 | }
13 |
14 | lintOptions {
15 | abortOnError false
16 | }
17 | }
18 |
19 | dependencies {
20 | implementation libraries.annotations
21 | }
22 |
23 | //apply from: 'https://raw.github.com/chrisbanes/gradle-mvn-push/master/gradle-mvn-push.gradle'
24 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | VERSION_NAME=1.2.0
2 | VERSION_CODE=24
3 | BUILD_TOOLS_VERSION=26.0.1
4 | COMPILE_SDK_VERSION=26
5 | GROUP=com.github.castorflex.smoothprogressbar
6 |
7 | POM_URL=https://github.com/castorflex/SmoothProgressBar
8 | POM_SCM_URL=https://github.com/castorflex/SmoothProgressBar
9 | POM_SCM_CONNECTION=scm:git@github.com:castorflex/SmoothProgressBar.git
10 | POM_SCM_DEV_CONNECTION=scm:git@github.com:castorflex/SmoothProgressBar.git
11 | POM_LICENCE_NAME=The Apache Software License, Version 2.0
12 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
13 | POM_LICENCE_DIST=repo
14 | POM_DEVELOPER_ID=castorflex
15 | POM_DEVELOPER_NAME=Antoine Merle
16 |
17 |
--------------------------------------------------------------------------------
/library-circular/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion Integer.parseInt(project.COMPILE_SDK_VERSION)
5 | buildToolsVersion project.BUILD_TOOLS_VERSION
6 |
7 | defaultConfig {
8 | minSdkVersion 14
9 | targetSdkVersion 26
10 | versionName project.VERSION_NAME
11 | versionCode Integer.parseInt(project.VERSION_CODE)
12 | }
13 |
14 | lintOptions {
15 | abortOnError false
16 | }
17 | }
18 |
19 | dependencies {
20 | implementation libraries.annotations
21 | }
22 |
23 | //apply from: 'https://raw.github.com/chrisbanes/gradle-mvn-push/master/gradle-mvn-push.gradle'
24 |
--------------------------------------------------------------------------------
/library/src/main/res/values/defaults.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #33b5e5
4 | 4dp
5 | 4dp
6 | 4
7 | 0
8 | - 1
9 | false
10 | false
11 | false
12 |
13 |
--------------------------------------------------------------------------------
/library-circular/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
10 |
11 |
19 |
20 |
--------------------------------------------------------------------------------
/sample/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/library/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
9 |
19 |
--------------------------------------------------------------------------------
/library-circular/src/main/java/fr.castorflex.android.circularprogressbar/SimpleAnimatorListener.java:
--------------------------------------------------------------------------------
1 | package fr.castorflex.android.circularprogressbar;
2 |
3 | import android.animation.Animator;
4 | import android.support.annotation.CallSuper;
5 |
6 | abstract class SimpleAnimatorListener implements Animator.AnimatorListener{
7 | private boolean mStarted = false;
8 | private boolean mCancelled = false;
9 |
10 | @Override
11 | @CallSuper
12 | public void onAnimationStart(Animator animation) {
13 | mCancelled = false;
14 | mStarted = true;
15 | }
16 |
17 | @Override
18 | public final void onAnimationEnd(Animator animation) {
19 | onPreAnimationEnd(animation);
20 | mStarted = false;
21 | }
22 |
23 | protected void onPreAnimationEnd(Animator animation) {
24 | }
25 |
26 | @Override
27 | @CallSuper
28 | public void onAnimationCancel(Animator animation) {
29 | mCancelled = true;
30 | }
31 |
32 | @Override
33 | public void onAnimationRepeat(Animator animation) {
34 |
35 | }
36 |
37 | public boolean isStartedAndNotCancelled() {
38 | return mStarted && !mCancelled;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/library/src/main/res/values/about_libraries_def.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Antoine Merle
5 | https://github.com/castorflex
6 | SmoothProgressBar
7 | Small library allowing you to make a smooth indeterminate progress bar. You can either user your progress bars and set this drawable or use directly the SmoothProgressBarView.
8 | 1.0.0
9 | https://github.com/castorflex/SmoothProgressBar
10 | apache_2_0
11 | true
12 | https://github.com/castorflex/SmoothProgressBar.git
13 |
14 |
--------------------------------------------------------------------------------
/library/src/main/java/fr/castorflex/android/smoothprogressbar/ColorsShape.java:
--------------------------------------------------------------------------------
1 | package fr.castorflex.android.smoothprogressbar;
2 |
3 | import android.graphics.Canvas;
4 | import android.graphics.Paint;
5 | import android.graphics.drawable.shapes.Shape;
6 |
7 | /**
8 | * Created by castorflex on 3/5/14.
9 | */
10 | public class ColorsShape extends Shape {
11 |
12 | private float mStrokeWidth;
13 | private int[] mColors;
14 |
15 | public ColorsShape(float strokeWidth, int[] colors) {
16 | mStrokeWidth = strokeWidth;
17 | mColors = colors;
18 | }
19 |
20 | public float getStrokeWidth() {
21 | return mStrokeWidth;
22 | }
23 |
24 | public void setStrokeWidth(float strokeWidth) {
25 | mStrokeWidth = strokeWidth;
26 | }
27 |
28 | public int[] getColors() {
29 | return mColors;
30 | }
31 |
32 | public void setColors(int[] colors) {
33 | mColors = colors;
34 | }
35 |
36 | @Override
37 | public void draw(Canvas canvas, Paint paint) {
38 | float ratio = 1f / mColors.length;
39 | int i = 0;
40 | paint.setStrokeWidth(mStrokeWidth);
41 | for (int color : mColors) {
42 | paint.setColor(color);
43 | canvas.drawLine(i * ratio * getWidth(), getHeight() / 2, ++i * ratio * getWidth(), getHeight() / 2, paint);
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/library-circular/src/main/java/fr.castorflex.android.circularprogressbar/Options.java:
--------------------------------------------------------------------------------
1 | package fr.castorflex.android.circularprogressbar;
2 |
3 | import android.view.animation.Interpolator;
4 |
5 | class Options {
6 |
7 | //params
8 | final Interpolator angleInterpolator;
9 | final Interpolator sweepInterpolator;
10 | final float borderWidth;
11 | final int[] colors;
12 | final float sweepSpeed;
13 | final float rotationSpeed;
14 | final int minSweepAngle;
15 | final int maxSweepAngle;
16 | @CircularProgressDrawable.Style final int style;
17 |
18 | Options(Interpolator angleInterpolator,
19 | Interpolator sweepInterpolator,
20 | float borderWidth,
21 | int[] colors,
22 | float sweepSpeed,
23 | float rotationSpeed,
24 | int minSweepAngle,
25 | int maxSweepAngle,
26 | @CircularProgressDrawable.Style int style) {
27 | this.angleInterpolator = angleInterpolator;
28 | this.sweepInterpolator = sweepInterpolator;
29 | this.borderWidth = borderWidth;
30 | this.colors = colors;
31 | this.sweepSpeed = sweepSpeed;
32 | this.rotationSpeed = rotationSpeed;
33 | this.minSweepAngle = minSweepAngle;
34 | this.maxSweepAngle = maxSweepAngle;
35 | this.style = style;
36 | }
37 |
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/library-circular/src/main/java/fr.castorflex.android.circularprogressbar/LookupTableInterpolator.java:
--------------------------------------------------------------------------------
1 | package fr.castorflex.android.circularprogressbar;
2 |
3 | import android.view.animation.Interpolator;
4 |
5 | /**
6 | * Backport
7 | */
8 | abstract class LookupTableInterpolator implements Interpolator {
9 |
10 | private final float[] mValues;
11 | private final float mStepSize;
12 |
13 | LookupTableInterpolator(float[] values) {
14 | mValues = values;
15 | mStepSize = 1f / (mValues.length - 1);
16 | }
17 |
18 | @Override
19 | public float getInterpolation(float input) {
20 | if (input >= 1.0f) {
21 | return 1.0f;
22 | }
23 | if (input <= 0f) {
24 | return 0f;
25 | }
26 |
27 | // Calculate index - We use min with length - 2 to avoid IndexOutOfBoundsException when
28 | // we lerp (linearly interpolate) in the return statement
29 | int position = Math.min((int) (input * (mValues.length - 1)), mValues.length - 2);
30 |
31 | // Calculate values to account for small offsets as the lookup table has discrete values
32 | float quantized = position * mStepSize;
33 | float diff = input - quantized;
34 | float weight = diff / mStepSize;
35 |
36 | // Linearly interpolate between the table values
37 | return mValues[position] + weight * (mValues[position + 1] - mValues[position]);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/sample/src/main/java/fr/castorflex/android/smoothprogressbar/sample/LookupTableInterpolator.java:
--------------------------------------------------------------------------------
1 | package fr.castorflex.android.smoothprogressbar.sample;
2 |
3 | import android.view.animation.Interpolator;
4 |
5 | /**
6 | * Backport
7 | */
8 | abstract class LookupTableInterpolator implements Interpolator {
9 |
10 | private final float[] mValues;
11 | private final float mStepSize;
12 |
13 | public LookupTableInterpolator(float[] values) {
14 | mValues = values;
15 | mStepSize = 1f / (mValues.length - 1);
16 | }
17 |
18 | @Override
19 | public float getInterpolation(float input) {
20 | if (input >= 1.0f) {
21 | return 1.0f;
22 | }
23 | if (input <= 0f) {
24 | return 0f;
25 | }
26 |
27 | // Calculate index - We use min with length - 2 to avoid IndexOutOfBoundsException when
28 | // we lerp (linearly interpolate) in the return statement
29 | int position = Math.min((int) (input * (mValues.length - 1)), mValues.length - 2);
30 |
31 | // Calculate values to account for small offsets as the lookup table has discrete values
32 | float quantized = position * mStepSize;
33 | float diff = input - quantized;
34 | float weight = diff / mStepSize;
35 |
36 | // Linearly interpolate between the table values
37 | return mValues[position] + weight * (mValues[position + 1] - mValues[position]);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/sample/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion Integer.parseInt(project.COMPILE_SDK_VERSION)
5 | buildToolsVersion project.BUILD_TOOLS_VERSION
6 |
7 | defaultConfig {
8 | minSdkVersion 14
9 | targetSdkVersion 26
10 | versionName project.VERSION_NAME
11 | versionCode Integer.parseInt(project.VERSION_CODE)
12 | }
13 |
14 | signingConfigs {
15 | release
16 | }
17 | buildTypes {
18 | debug {}
19 |
20 | release {
21 | signingConfig project.hasProperty('KEYSTORE_STORE_FILE') ? signingConfigs.release : signingConfigs.debug
22 | }
23 | }
24 |
25 | lintOptions {
26 | abortOnError false
27 | }
28 |
29 | }
30 |
31 | dependencies {
32 | implementation project(':library')
33 | implementation project(':library-circular')
34 | }
35 |
36 |
37 | if (project.hasProperty('KEYSTORE_STORE_FILE')) {
38 | android.signingConfigs.release.storeFile = file(KEYSTORE_STORE_FILE)
39 | }
40 | if (project.hasProperty('KEYSTORE_KEY_ALIAS')) {
41 | android.signingConfigs.release.keyAlias = KEYSTORE_KEY_ALIAS
42 | }
43 | if (project.hasProperty('KEYSTORE_STORE_PASSWORD')) {
44 | android.signingConfigs.release.storePassword = KEYSTORE_STORE_PASSWORD
45 | }
46 | if (project.hasProperty('KEYSTORE_KEY_PASSWORD')) {
47 | android.signingConfigs.release.keyPassword = KEYSTORE_KEY_PASSWORD
48 | }
49 |
--------------------------------------------------------------------------------
/library/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/library-circular/src/main/java/fr.castorflex.android.circularprogressbar/PowerSaveModeDelegate.java:
--------------------------------------------------------------------------------
1 | package fr.castorflex.android.circularprogressbar;
2 |
3 | import android.graphics.Canvas;
4 | import android.graphics.Paint;
5 | import android.os.SystemClock;
6 | import android.support.annotation.NonNull;
7 |
8 | import java.util.concurrent.TimeUnit;
9 |
10 | /**
11 | * Created by castorflex on 9/12/15.
12 | */
13 | class PowerSaveModeDelegate implements PBDelegate {
14 | private static final long REFRESH_RATE = TimeUnit.SECONDS.toMillis(1L);
15 |
16 | private final CircularProgressDrawable mParent;
17 | private int mCurrentRotation;
18 |
19 | PowerSaveModeDelegate(@NonNull CircularProgressDrawable parent) {
20 | mParent = parent;
21 | }
22 |
23 | @Override
24 | public void draw(Canvas canvas, Paint paint) {
25 | canvas.drawArc(mParent.getDrawableBounds(), mCurrentRotation, 300, false, paint);
26 | }
27 |
28 | @Override
29 | public void start() {
30 | mParent.invalidate();
31 |
32 | mParent.scheduleSelf(mRunnable, SystemClock.uptimeMillis() + REFRESH_RATE);
33 | }
34 |
35 | @Override
36 | public void stop() {
37 | mParent.unscheduleSelf(mRunnable);
38 | }
39 |
40 | @Override
41 | public void progressiveStop(CircularProgressDrawable.OnEndListener listener) {
42 | mParent.stop();
43 | }
44 |
45 | private final Runnable mRunnable = new Runnable() {
46 | @Override
47 | public void run() {
48 | mCurrentRotation += 50;
49 | mCurrentRotation %= 360;
50 |
51 | if (mParent.isRunning())
52 | mParent.scheduleSelf(this, SystemClock.uptimeMillis() + REFRESH_RATE);
53 |
54 | mParent.invalidate();
55 | }
56 | };
57 | }
58 |
--------------------------------------------------------------------------------
/library/src/main/java/fr/castorflex/android/smoothprogressbar/SmoothProgressBarUtils.java:
--------------------------------------------------------------------------------
1 | package fr.castorflex.android.smoothprogressbar;
2 |
3 | import android.graphics.drawable.Drawable;
4 | import android.graphics.drawable.ShapeDrawable;
5 |
6 | import java.util.Locale;
7 |
8 | /**
9 | * Created by castorflex on 3/5/14.
10 | */
11 | public final class SmoothProgressBarUtils {
12 | private SmoothProgressBarUtils() {
13 | }
14 |
15 | public static Drawable generateDrawableWithColors(int[] colors, float strokeWidth) {
16 | if (colors == null || colors.length == 0) return null;
17 |
18 | return new ShapeDrawable(new ColorsShape(strokeWidth, colors));
19 | }
20 |
21 | static void checkSpeed(float speed) {
22 | if (speed <= 0f)
23 | throw new IllegalArgumentException("Speed must be >= 0");
24 | }
25 |
26 | static void checkColors(int[] colors) {
27 | if (colors == null || colors.length == 0)
28 | throw new IllegalArgumentException("You must provide at least 1 color");
29 | }
30 |
31 | static void checkAngle(int angle) {
32 | if (angle < 0 || angle > 360)
33 | throw new IllegalArgumentException(String.format(Locale.US, "Illegal angle %d: must be >=0 and <= 360", angle));
34 | }
35 |
36 | static void checkPositiveOrZero(float number, String name) {
37 | if (number < 0)
38 | throw new IllegalArgumentException(String.format(Locale.US, "%s %d must be positive", name, number));
39 | }
40 |
41 | static void checkPositive(int number, String name){
42 | if(number <= 0)
43 | throw new IllegalArgumentException(String.format(Locale.US, "%s must not be null", name));
44 | }
45 |
46 | static void checkNotNull(Object o, String name) {
47 | if (o == null)
48 | throw new IllegalArgumentException(String.format(Locale.US, "%s must be not null", name));
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 1.2.0
2 |
3 | - Added new `FastOutSlowInInterpolator`, enabled by default
4 | - Fixed power save mode on circular progressbar
5 | - Fixed animators stop on circular progressbar
6 |
7 | ## 1.1.0
8 |
9 | - Fixed Circular bugs (flashes)
10 | - Added material themes (mainly for determinate support)
11 | - Fixed exception bug in preview mode
12 | - **/!\\** Removed a weird 48dp height in the SmoothProgressBar style
13 |
14 | ## 1.0.0
15 |
16 | - Added CircularProgressDrawable (min API 14)
17 |
18 | ## 0.5.1
19 |
20 | - Fixed bug with gradients when reversed mode enabled and mirror mode disabled
21 |
22 | ## 0.5.0
23 |
24 | - Added a gradient option via XML and JAVA
25 |
26 | ## 0.4.0
27 |
28 | - Added a `progressStart()` and `progressStop()` methods
29 | - Added parameter `progressStart_activated` which makes the drawable to animate progressively at each start
30 | - Added speed parameters for progressStart and progressStop
31 | - Added possibility to set a background for the progressiveStart/Stop (See Pocket example)
32 | - Added possibility to set a generated background according to the bar's colors
33 | - Added a listener `progressiveStopEndedListener`: called when the progressiveStop animation is over
34 |
35 | ## 0.3.3
36 |
37 | - Added a ContentLoadingSmoothProgressBar (see also [ContentLoadingProgressBar](https://android.googlesource.com/platform/frameworks/support/+/refs/heads/master/v4/java/android/support/v4/widget/ContentLoadingProgressBar.java))
38 |
39 | ## 0.3.2
40 |
41 | - targetSdkVersion is now 14. We just need holo style.
42 |
43 | ## 0.3.1
44 |
45 | - Added a `applyStyle(int styleResId)` method
46 |
47 | ## 0.3.0
48 |
49 | - `SmoothProgressDrawable.Builder#width` is now `setStrokeWidth`
50 | - The `strokeWidth` parameter is now a `float` (was an `int`)
51 | - Added possibility to modify dynamically the `SmoothProgressBar` and `SmoothProgressDrawable` properties.
52 | e.g. You can call `mProgressBar.setColor(mColor)`
53 |
--------------------------------------------------------------------------------
/sample/src/main/java/fr/castorflex/android/smoothprogressbar/sample/MainActivity.java:
--------------------------------------------------------------------------------
1 | package fr.castorflex.android.smoothprogressbar.sample;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.view.View;
7 | import android.view.animation.AccelerateInterpolator;
8 | import android.widget.ProgressBar;
9 |
10 | import fr.castorflex.android.smoothprogressbar.SmoothProgressBar;
11 | import fr.castorflex.android.smoothprogressbar.SmoothProgressBarUtils;
12 | import fr.castorflex.android.smoothprogressbar.SmoothProgressDrawable;
13 |
14 | public class MainActivity extends Activity {
15 |
16 | private ProgressBar mProgressBar1;
17 | private SmoothProgressBar mGoogleNow;
18 | private SmoothProgressBar mPocketBar;
19 |
20 | @Override
21 | protected void onCreate(Bundle savedInstanceState) {
22 | super.onCreate(savedInstanceState);
23 | setContentView(R.layout.activity_main);
24 |
25 | mProgressBar1 = (ProgressBar) findViewById(R.id.progressbar2);
26 | mPocketBar = (SmoothProgressBar) findViewById(R.id.pocket);
27 |
28 | mProgressBar1.setIndeterminateDrawable(new SmoothProgressDrawable.Builder(this).interpolator(new AccelerateInterpolator()).build());
29 |
30 | mGoogleNow = (SmoothProgressBar) findViewById(R.id.google_now);
31 | mPocketBar.setSmoothProgressDrawableBackgroundDrawable(
32 | SmoothProgressBarUtils.generateDrawableWithColors(
33 | getResources().getIntArray(R.array.pocket_background_colors),
34 | ((SmoothProgressDrawable) mPocketBar.getIndeterminateDrawable()).getStrokeWidth()));
35 |
36 | findViewById(R.id.button_make).setOnClickListener(new View.OnClickListener() {
37 | @Override
38 | public void onClick(View v) {
39 | Intent intent = new Intent(MainActivity.this, MakeCustomActivity.class);
40 | startActivity(intent);
41 | }
42 | });
43 |
44 | findViewById(R.id.start).setOnClickListener(new View.OnClickListener() {
45 | @Override
46 | public void onClick(View v) {
47 | mPocketBar.progressiveStart();
48 | }
49 | });
50 |
51 | findViewById(R.id.finish).setOnClickListener(new View.OnClickListener() {
52 | @Override
53 | public void onClick(View v) {
54 | mPocketBar.progressiveStop();
55 | }
56 | });
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - @color/holo_blue_dark
5 | - @color/holo_yellow_dark
6 | - @color/holo_green_dark
7 | - @color/holo_purple_dark
8 | - @color/holo_red_dark
9 |
10 |
11 |
12 | - @color/gplus_color_1
13 | - @color/gplus_color_2
14 | - @color/gplus_color_3
15 | - @color/gplus_color_4
16 |
17 |
18 |
19 | - @color/pocket_color_1
20 | - @color/pocket_color_1
21 | - @color/pocket_color_1
22 | - @color/pocket_color_1
23 | - @color/pocket_color_2
24 | - @color/pocket_color_2
25 | - @color/pocket_color_2
26 | - @color/pocket_color_2
27 | - @color/pocket_color_3
28 | - @color/pocket_color_3
29 | - @color/pocket_color_3
30 | - @color/pocket_color_3
31 | - @color/pocket_color_4
32 | - @color/pocket_color_4
33 | - @color/pocket_color_4
34 | - @color/pocket_color_4
35 |
36 |
37 |
38 | - @color/pocket_color_1
39 | - @color/pocket_color_2
40 | - @color/pocket_color_3
41 | - @color/pocket_color_4
42 |
43 |
44 | #85edb9
45 | #34bdb7
46 | #ee4458
47 | #fcb74d
48 |
49 | #3e802f
50 | #f4b400
51 | #427fed
52 | #b23424
53 |
54 | #0099cc
55 | #ff8800
56 | #669900
57 | #9933cc
58 | #cc0000
59 |
--------------------------------------------------------------------------------
/library-circular/src/main/java/fr.castorflex.android.circularprogressbar/FastOutSlowInInterpolator.java:
--------------------------------------------------------------------------------
1 | package fr.castorflex.android.circularprogressbar;
2 |
3 | /**
4 | * Backport
5 | */
6 | class FastOutSlowInInterpolator extends LookupTableInterpolator {
7 |
8 | private static final float[] VALUES = new float[] {
9 | 0.0000f, 0.0001f, 0.0002f, 0.0005f, 0.0009f, 0.0014f, 0.0020f,
10 | 0.0027f, 0.0036f, 0.0046f, 0.0058f, 0.0071f, 0.0085f, 0.0101f,
11 | 0.0118f, 0.0137f, 0.0158f, 0.0180f, 0.0205f, 0.0231f, 0.0259f,
12 | 0.0289f, 0.0321f, 0.0355f, 0.0391f, 0.0430f, 0.0471f, 0.0514f,
13 | 0.0560f, 0.0608f, 0.0660f, 0.0714f, 0.0771f, 0.0830f, 0.0893f,
14 | 0.0959f, 0.1029f, 0.1101f, 0.1177f, 0.1257f, 0.1339f, 0.1426f,
15 | 0.1516f, 0.1610f, 0.1707f, 0.1808f, 0.1913f, 0.2021f, 0.2133f,
16 | 0.2248f, 0.2366f, 0.2487f, 0.2611f, 0.2738f, 0.2867f, 0.2998f,
17 | 0.3131f, 0.3265f, 0.3400f, 0.3536f, 0.3673f, 0.3810f, 0.3946f,
18 | 0.4082f, 0.4217f, 0.4352f, 0.4485f, 0.4616f, 0.4746f, 0.4874f,
19 | 0.5000f, 0.5124f, 0.5246f, 0.5365f, 0.5482f, 0.5597f, 0.5710f,
20 | 0.5820f, 0.5928f, 0.6033f, 0.6136f, 0.6237f, 0.6335f, 0.6431f,
21 | 0.6525f, 0.6616f, 0.6706f, 0.6793f, 0.6878f, 0.6961f, 0.7043f,
22 | 0.7122f, 0.7199f, 0.7275f, 0.7349f, 0.7421f, 0.7491f, 0.7559f,
23 | 0.7626f, 0.7692f, 0.7756f, 0.7818f, 0.7879f, 0.7938f, 0.7996f,
24 | 0.8053f, 0.8108f, 0.8162f, 0.8215f, 0.8266f, 0.8317f, 0.8366f,
25 | 0.8414f, 0.8461f, 0.8507f, 0.8551f, 0.8595f, 0.8638f, 0.8679f,
26 | 0.8720f, 0.8760f, 0.8798f, 0.8836f, 0.8873f, 0.8909f, 0.8945f,
27 | 0.8979f, 0.9013f, 0.9046f, 0.9078f, 0.9109f, 0.9139f, 0.9169f,
28 | 0.9198f, 0.9227f, 0.9254f, 0.9281f, 0.9307f, 0.9333f, 0.9358f,
29 | 0.9382f, 0.9406f, 0.9429f, 0.9452f, 0.9474f, 0.9495f, 0.9516f,
30 | 0.9536f, 0.9556f, 0.9575f, 0.9594f, 0.9612f, 0.9629f, 0.9646f,
31 | 0.9663f, 0.9679f, 0.9695f, 0.9710f, 0.9725f, 0.9739f, 0.9753f,
32 | 0.9766f, 0.9779f, 0.9791f, 0.9803f, 0.9815f, 0.9826f, 0.9837f,
33 | 0.9848f, 0.9858f, 0.9867f, 0.9877f, 0.9885f, 0.9894f, 0.9902f,
34 | 0.9910f, 0.9917f, 0.9924f, 0.9931f, 0.9937f, 0.9944f, 0.9949f,
35 | 0.9955f, 0.9960f, 0.9964f, 0.9969f, 0.9973f, 0.9977f, 0.9980f,
36 | 0.9984f, 0.9986f, 0.9989f, 0.9991f, 0.9993f, 0.9995f, 0.9997f,
37 | 0.9998f, 0.9999f, 0.9999f, 1.0000f, 1.0000f
38 | };
39 | FastOutSlowInInterpolator() {
40 | super(VALUES);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/sample/src/main/java/fr/castorflex/android/smoothprogressbar/sample/FastOutSlowInInterpolator.java:
--------------------------------------------------------------------------------
1 | package fr.castorflex.android.smoothprogressbar.sample;
2 |
3 | /**
4 | * Backport
5 | */
6 | class FastOutSlowInInterpolator extends LookupTableInterpolator {
7 |
8 | private static final float[] VALUES = new float[] {
9 | 0.0000f, 0.0001f, 0.0002f, 0.0005f, 0.0009f, 0.0014f, 0.0020f,
10 | 0.0027f, 0.0036f, 0.0046f, 0.0058f, 0.0071f, 0.0085f, 0.0101f,
11 | 0.0118f, 0.0137f, 0.0158f, 0.0180f, 0.0205f, 0.0231f, 0.0259f,
12 | 0.0289f, 0.0321f, 0.0355f, 0.0391f, 0.0430f, 0.0471f, 0.0514f,
13 | 0.0560f, 0.0608f, 0.0660f, 0.0714f, 0.0771f, 0.0830f, 0.0893f,
14 | 0.0959f, 0.1029f, 0.1101f, 0.1177f, 0.1257f, 0.1339f, 0.1426f,
15 | 0.1516f, 0.1610f, 0.1707f, 0.1808f, 0.1913f, 0.2021f, 0.2133f,
16 | 0.2248f, 0.2366f, 0.2487f, 0.2611f, 0.2738f, 0.2867f, 0.2998f,
17 | 0.3131f, 0.3265f, 0.3400f, 0.3536f, 0.3673f, 0.3810f, 0.3946f,
18 | 0.4082f, 0.4217f, 0.4352f, 0.4485f, 0.4616f, 0.4746f, 0.4874f,
19 | 0.5000f, 0.5124f, 0.5246f, 0.5365f, 0.5482f, 0.5597f, 0.5710f,
20 | 0.5820f, 0.5928f, 0.6033f, 0.6136f, 0.6237f, 0.6335f, 0.6431f,
21 | 0.6525f, 0.6616f, 0.6706f, 0.6793f, 0.6878f, 0.6961f, 0.7043f,
22 | 0.7122f, 0.7199f, 0.7275f, 0.7349f, 0.7421f, 0.7491f, 0.7559f,
23 | 0.7626f, 0.7692f, 0.7756f, 0.7818f, 0.7879f, 0.7938f, 0.7996f,
24 | 0.8053f, 0.8108f, 0.8162f, 0.8215f, 0.8266f, 0.8317f, 0.8366f,
25 | 0.8414f, 0.8461f, 0.8507f, 0.8551f, 0.8595f, 0.8638f, 0.8679f,
26 | 0.8720f, 0.8760f, 0.8798f, 0.8836f, 0.8873f, 0.8909f, 0.8945f,
27 | 0.8979f, 0.9013f, 0.9046f, 0.9078f, 0.9109f, 0.9139f, 0.9169f,
28 | 0.9198f, 0.9227f, 0.9254f, 0.9281f, 0.9307f, 0.9333f, 0.9358f,
29 | 0.9382f, 0.9406f, 0.9429f, 0.9452f, 0.9474f, 0.9495f, 0.9516f,
30 | 0.9536f, 0.9556f, 0.9575f, 0.9594f, 0.9612f, 0.9629f, 0.9646f,
31 | 0.9663f, 0.9679f, 0.9695f, 0.9710f, 0.9725f, 0.9739f, 0.9753f,
32 | 0.9766f, 0.9779f, 0.9791f, 0.9803f, 0.9815f, 0.9826f, 0.9837f,
33 | 0.9848f, 0.9858f, 0.9867f, 0.9877f, 0.9885f, 0.9894f, 0.9902f,
34 | 0.9910f, 0.9917f, 0.9924f, 0.9931f, 0.9937f, 0.9944f, 0.9949f,
35 | 0.9955f, 0.9960f, 0.9964f, 0.9969f, 0.9973f, 0.9977f, 0.9980f,
36 | 0.9984f, 0.9986f, 0.9989f, 0.9991f, 0.9993f, 0.9995f, 0.9997f,
37 | 0.9998f, 0.9999f, 0.9999f, 1.0000f, 1.0000f
38 | };
39 | public FastOutSlowInInterpolator() {
40 | super(VALUES);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/library-circular/src/main/java/fr.castorflex.android.circularprogressbar/Utils.java:
--------------------------------------------------------------------------------
1 | package fr.castorflex.android.circularprogressbar;
2 |
3 | import android.animation.ValueAnimator;
4 | import android.annotation.TargetApi;
5 | import android.content.Context;
6 | import android.os.Build;
7 | import android.os.PowerManager;
8 | import android.support.annotation.NonNull;
9 |
10 | import java.util.Locale;
11 |
12 | import static java.lang.Math.min;
13 |
14 | class Utils {
15 |
16 | private Utils() {
17 | }
18 |
19 | static void checkSpeed(float speed) {
20 | if (speed <= 0f)
21 | throw new IllegalArgumentException("Speed must be >= 0");
22 | }
23 |
24 | static void checkColors(int[] colors) {
25 | if (colors == null || colors.length == 0)
26 | throw new IllegalArgumentException("You must provide at least 1 color");
27 | }
28 |
29 | static void checkAngle(int angle) {
30 | if (angle < 0 || angle > 360)
31 | throw new IllegalArgumentException(String.format(Locale.US, "Illegal angle %d: must be >=0 and <=360", angle));
32 | }
33 |
34 | static void checkPositiveOrZero(float number, String name) {
35 | if (number < 0)
36 | throw new IllegalArgumentException(String.format(Locale.US, "%s %f must be positive", name, number));
37 | }
38 |
39 | static void checkPositive(int number, String name) {
40 | if (number <= 0)
41 | throw new IllegalArgumentException(String.format(Locale.US, "%s must not be null", name));
42 | }
43 |
44 | static void checkNotNull(Object o, String name) {
45 | if (o == null)
46 | throw new IllegalArgumentException(String.format(Locale.US, "%s must be not null", name));
47 | }
48 |
49 | static float getAnimatedFraction(ValueAnimator animator) {
50 | float fraction;
51 | if (Build.VERSION.SDK_INT >= 23) {
52 | fraction = animator.getAnimatedFraction();
53 | } else {
54 | fraction = animator.getDuration() > 0 ? ((float) animator.getCurrentPlayTime()) / animator.getDuration() : 0f;
55 | fraction = min(fraction, 1f);
56 | fraction = animator.getInterpolator().getInterpolation(fraction);
57 | }
58 |
59 | return fraction;
60 | }
61 |
62 | @TargetApi(21)
63 | static boolean isPowerSaveModeEnabled(@NonNull PowerManager powerManager) {
64 | if (Build.VERSION.SDK_INT < 21) return false;
65 |
66 | try {
67 | return powerManager.isPowerSaveMode();
68 | } catch (Exception e) {
69 | return false;
70 | }
71 | }
72 |
73 | static PowerManager powerManager(Context context) {
74 | return (PowerManager) context.getSystemService(Context.POWER_SERVICE);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
10 |
11 |
18 |
19 |
32 |
33 |
41 |
42 |
51 |
52 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/library-circular/src/main/java/fr.castorflex.android.circularprogressbar/CircularProgressBar.java:
--------------------------------------------------------------------------------
1 | package fr.castorflex.android.circularprogressbar;
2 |
3 | import android.content.Context;
4 | import android.content.res.Resources;
5 | import android.content.res.TypedArray;
6 | import android.graphics.drawable.Drawable;
7 | import android.util.AttributeSet;
8 | import android.widget.ProgressBar;
9 |
10 | public class CircularProgressBar extends ProgressBar {
11 |
12 | public CircularProgressBar(Context context) {
13 | this(context, null);
14 | }
15 |
16 | public CircularProgressBar(Context context, AttributeSet attrs) {
17 | this(context, attrs, R.attr.cpbStyle);
18 | }
19 |
20 | public CircularProgressBar(Context context, AttributeSet attrs, int defStyle) {
21 | super(context, attrs, defStyle);
22 |
23 | if (isInEditMode()) {
24 | setIndeterminateDrawable(new CircularProgressDrawable.Builder(context, true).build());
25 | return;
26 | }
27 |
28 | Resources res = context.getResources();
29 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircularProgressBar, defStyle, 0);
30 |
31 |
32 | final int color = a.getColor(R.styleable.CircularProgressBar_cpb_color, res.getColor(R.color.cpb_default_color));
33 | final float strokeWidth = a.getDimension(R.styleable.CircularProgressBar_cpb_stroke_width, res.getDimension(R.dimen.cpb_default_stroke_width));
34 | final float sweepSpeed = a.getFloat(R.styleable.CircularProgressBar_cpb_sweep_speed, Float.parseFloat(res.getString(R.string.cpb_default_sweep_speed)));
35 | final float rotationSpeed = a.getFloat(R.styleable.CircularProgressBar_cpb_rotation_speed, Float.parseFloat(res.getString(R.string.cpb_default_rotation_speed)));
36 | final int colorsId = a.getResourceId(R.styleable.CircularProgressBar_cpb_colors, 0);
37 | final int minSweepAngle = a.getInteger(R.styleable.CircularProgressBar_cpb_min_sweep_angle, res.getInteger(R.integer.cpb_default_min_sweep_angle));
38 | final int maxSweepAngle = a.getInteger(R.styleable.CircularProgressBar_cpb_max_sweep_angle, res.getInteger(R.integer.cpb_default_max_sweep_angle));
39 | a.recycle();
40 |
41 | int[] colors = null;
42 | //colors
43 | if (colorsId != 0) {
44 | colors = res.getIntArray(colorsId);
45 | }
46 |
47 | Drawable indeterminateDrawable;
48 | CircularProgressDrawable.Builder builder = new CircularProgressDrawable.Builder(context)
49 | .sweepSpeed(sweepSpeed)
50 | .rotationSpeed(rotationSpeed)
51 | .strokeWidth(strokeWidth)
52 | .minSweepAngle(minSweepAngle)
53 | .maxSweepAngle(maxSweepAngle);
54 |
55 | if (colors != null && colors.length > 0)
56 | builder.colors(colors);
57 | else
58 | builder.color(color);
59 |
60 | indeterminateDrawable = builder.build();
61 | setIndeterminateDrawable(indeterminateDrawable);
62 | }
63 |
64 | private CircularProgressDrawable checkIndeterminateDrawable() {
65 | Drawable ret = getIndeterminateDrawable();
66 | if (ret == null || !(ret instanceof CircularProgressDrawable))
67 | throw new RuntimeException("The drawable is not a CircularProgressDrawable");
68 | return (CircularProgressDrawable) ret;
69 | }
70 |
71 | public void progressiveStop() {
72 | checkIndeterminateDrawable().progressiveStop();
73 | }
74 |
75 | public void progressiveStop(CircularProgressDrawable.OnEndListener listener) {
76 | checkIndeterminateDrawable().progressiveStop(listener);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/library/src/main/java/fr/castorflex/android/smoothprogressbar/ContentLoadingSmoothProgressBar.java:
--------------------------------------------------------------------------------
1 | package fr.castorflex.android.smoothprogressbar;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.View;
6 |
7 | /**
8 | * This is a copy of the ContentLoadingProgressBar from the support library, but extends
9 | * SmoothProgressBar.
10 | */
11 | public class ContentLoadingSmoothProgressBar extends SmoothProgressBar {
12 |
13 | private static final int MIN_SHOW_TIME = 500; // ms
14 | private static final int MIN_DELAY = 500; // ms
15 |
16 | private long mStartTime = -1;
17 |
18 | private boolean mPostedHide = false;
19 |
20 | private boolean mPostedShow = false;
21 |
22 | private boolean mDismissed = false;
23 |
24 | private final Runnable mDelayedHide = new Runnable() {
25 |
26 | @Override
27 | public void run() {
28 | mPostedHide = false;
29 | mStartTime = -1;
30 | setVisibility(View.GONE);
31 | }
32 | };
33 |
34 | private final Runnable mDelayedShow = new Runnable() {
35 |
36 | @Override
37 | public void run() {
38 | mPostedShow = false;
39 | if (!mDismissed) {
40 | mStartTime = System.currentTimeMillis();
41 | setVisibility(View.VISIBLE);
42 | }
43 | }
44 | };
45 |
46 | public ContentLoadingSmoothProgressBar(Context context) {
47 | this(context, null);
48 | }
49 |
50 | public ContentLoadingSmoothProgressBar(Context context, AttributeSet attrs) {
51 | super(context, attrs, 0);
52 | }
53 |
54 | @Override
55 | public void onAttachedToWindow() {
56 | super.onAttachedToWindow();
57 | removeCallbacks();
58 | }
59 |
60 | @Override
61 | public void onDetachedFromWindow() {
62 | super.onDetachedFromWindow();
63 | removeCallbacks();
64 | }
65 |
66 | private void removeCallbacks() {
67 | removeCallbacks(mDelayedHide);
68 | removeCallbacks(mDelayedShow);
69 | }
70 |
71 | /**
72 | * Hide the progress view if it is visible. The progress view will not be
73 | * hidden until it has been shown for at least a minimum show time. If the
74 | * progress view was not yet visible, cancels showing the progress view.
75 | */
76 | public void hide() {
77 | mDismissed = true;
78 | removeCallbacks(mDelayedShow);
79 | long diff = System.currentTimeMillis() - mStartTime;
80 | if (diff >= MIN_SHOW_TIME || mStartTime == -1) {
81 | // The progress spinner has been shown long enough
82 | // OR was not shown yet. If it wasn't shown yet,
83 | // it will just never be shown.
84 | setVisibility(View.GONE);
85 | } else {
86 | // The progress spinner is shown, but not long enough,
87 | // so put a delayed message in to hide it when its been
88 | // shown long enough.
89 | if (!mPostedHide) {
90 | postDelayed(mDelayedHide, MIN_SHOW_TIME - diff);
91 | mPostedHide = true;
92 | }
93 | }
94 | }
95 |
96 | /**
97 | * Show the progress view after waiting for a minimum delay. If
98 | * during that time, hide() is called, the view is never made visible.
99 | */
100 | public void show() {
101 | // Reset the start time.
102 | mStartTime = -1;
103 | mDismissed = false;
104 | removeCallbacks(mDelayedHide);
105 | if (!mPostedShow) {
106 | postDelayed(mDelayedShow, MIN_DELAY);
107 | mPostedShow = true;
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
16 |
17 |
22 |
23 |
27 |
28 |
34 |
35 |
39 |
40 |
46 |
47 |
51 |
52 |
57 |
58 |
62 |
63 |
69 |
70 |
74 |
75 |
81 |
82 |
86 |
87 |
92 |
93 |
97 |
98 |
104 |
105 |
109 |
110 |
117 |
118 |
125 |
126 |
127 |
128 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_custom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
13 |
14 |
25 |
26 |
36 |
37 |
38 |
43 |
44 |
48 |
49 |
50 |
53 |
54 |
60 |
61 |
67 |
68 |
74 |
75 |
76 |
77 |
82 |
83 |
88 |
89 |
94 |
95 |
100 |
101 |
106 |
107 |
112 |
113 |
118 |
119 |
124 |
125 |
130 |
131 |
136 |
137 |
142 |
143 |
147 |
148 |
155 |
156 |
163 |
164 |
165 |
166 |
167 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Description
2 |
3 | Small library allowing you to make a smooth indeterminate progress bar. You can either user your progress bars and set this drawable or use directly the `SmoothProgressBarView`.
4 |
5 | ## Demo:
6 | Sample app available on the [Play Store]
7 |
8 | 
9 |
10 | ## How does it work
11 |
12 | I wrote a [blog post] about that.
13 |
14 | ## Integration
15 |
16 | **SmoothProgressBar** (min API 7): [](https://maven-badges.herokuapp.com/maven-central/com.github.castorflex.smoothprogressbar/library)
17 |
18 | **CircularProgressBar** (min API 14): [](https://maven-badges.herokuapp.com/maven-central/com.github.castorflex.smoothprogressbar/library-circular)
19 |
20 | The lib is now on Maven Central. All you have to do is add it on your gradle build:
21 |
22 | ```groovy
23 | dependencies {
24 | // of course, do not write x.x.x but the version number
25 | implementation 'com.github.castorflex.smoothprogressbar:library:x.x.x'
26 | // or
27 | implementation 'com.github.castorflex.smoothprogressbar:library-circular:x.x.x'
28 | }
29 | ```
30 | You can find the last stable version on [Gradle Please]
31 |
32 |
33 | Or you can try the latest snapshots:  
34 |
35 | ```groovy
36 | repositories {
37 | maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
38 | }
39 |
40 | dependencies {
41 | implementation 'com.github.castorflex.smoothprogressbar:library:1.4.0-SNAPSHOT'
42 | implementation 'com.github.castorflex.smoothprogressbar:library-circular:1.4.0-SNAPSHOT'
43 | }
44 | ```
45 |
46 |
47 | If you really want (or have) to use Eclipse, please look at the forks.
48 |
49 | ## Usage
50 |
51 | - Use directly SmoothProgressBar:
52 |
53 | ```xml
54 |
71 |
72 |
86 | ```
87 |
88 | Or use styles:
89 |
90 | ```xml
91 |
95 |
96 |
108 |
109 |
117 | ```
118 |
119 | *You can find more styles [in the sample app][Sample Themes]*
120 |
121 | - Or instantiate a `SmoothProgressDrawable`/`CircularProgressDrawable` and set it to your ProgressBar
122 |
123 | ```java
124 | mProgressBar.setIndeterminateDrawable(new SmoothProgressDrawable.Builder(context)
125 | .color(0xff0000)
126 | .interpolator(new DecelerateInterpolator())
127 | .sectionsCount(4)
128 | .separatorLength(8) //You should use Resources#getDimensionPixelSize
129 | .strokeWidth(8f) //You should use Resources#getDimension
130 | .speed(2f) //2 times faster
131 | .progressiveStartSpeed(2)
132 | .progressiveStopSpeed(3.4f)
133 | .reversed(false)
134 | .mirrorMode(false)
135 | .progressiveStart(true)
136 | .progressiveStopEndedListener(mListener) //called when the stop animation is over
137 | .build());
138 |
139 | mProgressBar.setIndeterminateDrawable(new CircularProgressDrawable
140 | .Builder(this)
141 | .colors(getResources().getIntArray(R.array.gplus_colors))
142 | .sweepSpeed(1f)
143 | .strokeWidth(mStrokeWidth)
144 | .style(CircularProgressDrawable.Style.ROUNDED)
145 | [ ... ]
146 | .build();
147 | ```
148 |
149 | You can also set many colors for one bar (see G+ app)
150 |
151 | - via xml (use the `app:spb_colors` attribute with a `integer-array` reference for that)
152 |
153 | - programmatically (use `SmoothProgressDrawable.Builder#colors(int[])` method).
154 |
155 |
156 | ## License
157 |
158 | ```
159 | Copyright 2014 Antoine Merle
160 |
161 | Licensed under the Apache License, Version 2.0 (the "License");
162 | you may not use this file except in compliance with the License.
163 | You may obtain a copy of the License at
164 |
165 | http://www.apache.org/licenses/LICENSE-2.0
166 |
167 | Unless required by applicable law or agreed to in writing, software
168 | distributed under the License is distributed on an "AS IS" BASIS,
169 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
170 | See the License for the specific language governing permissions and
171 | limitations under the License.
172 | ```
173 |
174 | #### Badges
175 |
176 | CI master: 
177 |
178 | CI dev: 
179 |
180 |
181 | [blog post]: http://antoine-merle.com/blog/2013/11/12/make-your-progressbar-more-smooth/
182 |
183 | [Play Store]: https://play.google.com/store/apps/details?id=fr.castorflex.android.smoothprogressbar.sample
184 |
185 | [Gradle Please]: http://gradleplease.appspot.com/#smoothprogressbar
186 |
187 | [Sample Themes]: https://github.com/castorflex/SmoothProgressBar/blob/master/sample/src/main/res/values/styles.xml
188 |
--------------------------------------------------------------------------------
/library-circular/src/main/java/fr.castorflex.android.circularprogressbar/CircularProgressDrawable.java:
--------------------------------------------------------------------------------
1 | package fr.castorflex.android.circularprogressbar;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Color;
6 | import android.graphics.ColorFilter;
7 | import android.graphics.Paint;
8 | import android.graphics.PixelFormat;
9 | import android.graphics.Rect;
10 | import android.graphics.RectF;
11 | import android.graphics.drawable.Animatable;
12 | import android.graphics.drawable.Drawable;
13 | import android.os.PowerManager;
14 | import android.support.annotation.IntDef;
15 | import android.support.annotation.NonNull;
16 | import android.support.annotation.UiThread;
17 | import android.view.animation.Interpolator;
18 | import android.view.animation.LinearInterpolator;
19 |
20 | import java.lang.annotation.Retention;
21 | import java.lang.annotation.RetentionPolicy;
22 |
23 | import static fr.castorflex.android.circularprogressbar.Utils.checkAngle;
24 | import static fr.castorflex.android.circularprogressbar.Utils.checkColors;
25 | import static fr.castorflex.android.circularprogressbar.Utils.checkNotNull;
26 | import static fr.castorflex.android.circularprogressbar.Utils.checkPositiveOrZero;
27 | import static fr.castorflex.android.circularprogressbar.Utils.checkSpeed;
28 |
29 | public class CircularProgressDrawable
30 | extends Drawable
31 | implements Animatable {
32 |
33 | public interface OnEndListener {
34 | void onEnd(CircularProgressDrawable drawable);
35 | }
36 |
37 | public static final int STYLE_NORMAL = 0;
38 | public static final int STYLE_ROUNDED = 1;
39 |
40 | @Retention(RetentionPolicy.SOURCE)
41 | @IntDef({STYLE_NORMAL, STYLE_ROUNDED})
42 | public @interface Style {
43 |
44 | }
45 |
46 | private final RectF mBounds = new RectF();
47 |
48 | private final PowerManager mPowerManager;
49 | private final Options mOptions;
50 | private final Paint mPaint;
51 | private boolean mRunning;
52 | private PBDelegate mPBDelegate;
53 |
54 | /**
55 | * Private method, use #Builder instead
56 | */
57 | private CircularProgressDrawable(PowerManager powerManager, Options options) {
58 | mOptions = options;
59 |
60 | mPaint = new Paint();
61 | mPaint.setAntiAlias(true);
62 | mPaint.setStyle(Paint.Style.STROKE);
63 | mPaint.setStrokeWidth(options.borderWidth);
64 | mPaint.setStrokeCap(options.style == STYLE_ROUNDED ? Paint.Cap.ROUND : Paint.Cap.BUTT);
65 | mPaint.setColor(options.colors[0]);
66 | mPowerManager = powerManager;
67 |
68 | initDelegate();
69 | }
70 |
71 | @Override
72 | public void draw(@NonNull Canvas canvas) {
73 | if (isRunning()) mPBDelegate.draw(canvas, mPaint);
74 | }
75 |
76 | @Override
77 | public void setAlpha(int alpha) {
78 | mPaint.setAlpha(alpha);
79 | }
80 |
81 | @Override
82 | public void setColorFilter(ColorFilter cf) {
83 | mPaint.setColorFilter(cf);
84 | }
85 |
86 | @Override
87 | public int getOpacity() {
88 | return PixelFormat.TRANSLUCENT;
89 | }
90 |
91 | @Override
92 | protected void onBoundsChange(Rect bounds) {
93 | super.onBoundsChange(bounds);
94 | float border = mOptions.borderWidth;
95 | mBounds.left = bounds.left + border / 2f + .5f;
96 | mBounds.right = bounds.right - border / 2f - .5f;
97 | mBounds.top = bounds.top + border / 2f + .5f;
98 | mBounds.bottom = bounds.bottom - border / 2f - .5f;
99 | }
100 |
101 |
102 | @Override
103 | public void start() {
104 | initDelegate();
105 | mPBDelegate.start();
106 | mRunning = true;
107 | invalidateSelf();
108 | }
109 |
110 | /**
111 | * Inits the delegate. Create one if the delegate is null or not the right mode
112 | */
113 | private void initDelegate() {
114 | boolean powerSaveMode = Utils.isPowerSaveModeEnabled(mPowerManager);
115 | if (powerSaveMode) {
116 | if (mPBDelegate == null || !(mPBDelegate instanceof PowerSaveModeDelegate)) {
117 | if (mPBDelegate != null) mPBDelegate.stop();
118 | mPBDelegate = new PowerSaveModeDelegate(this);
119 | }
120 | } else {
121 | if (mPBDelegate == null || (mPBDelegate instanceof PowerSaveModeDelegate)) {
122 | if (mPBDelegate != null) mPBDelegate.stop();
123 | mPBDelegate = new DefaultDelegate(this, mOptions);
124 | }
125 | }
126 | }
127 |
128 | @Override
129 | public void stop() {
130 | mRunning = false;
131 | mPBDelegate.stop();
132 | invalidateSelf();
133 | }
134 |
135 | @UiThread
136 | void invalidate() {
137 | if (getCallback() == null) {
138 | stop(); // we don't want these animator to keep running...
139 | }
140 | invalidateSelf();
141 | }
142 |
143 | @Override
144 | public boolean isRunning() {
145 | return mRunning;
146 | }
147 |
148 | Paint getCurrentPaint() {
149 | return mPaint;
150 | }
151 |
152 | RectF getDrawableBounds() {
153 | return mBounds;
154 | }
155 |
156 | ////////////////////////////////////////////////////////////////////
157 | //Progressive stop
158 | ////////////////////////////////////////////////////////////////////
159 |
160 | public void progressiveStop(CircularProgressDrawable.OnEndListener listener) {
161 | mPBDelegate.progressiveStop(listener);
162 | }
163 |
164 | public void progressiveStop() {
165 | progressiveStop(null);
166 | }
167 |
168 | public static class Builder {
169 | private static final Interpolator DEFAULT_ROTATION_INTERPOLATOR = new LinearInterpolator();
170 | private static final Interpolator DEFAULT_SWEEP_INTERPOLATOR = new FastOutSlowInInterpolator();
171 |
172 | private Interpolator mSweepInterpolator = DEFAULT_SWEEP_INTERPOLATOR;
173 | private Interpolator mAngleInterpolator = DEFAULT_ROTATION_INTERPOLATOR;
174 | private float mBorderWidth;
175 | private int[] mColors;
176 | private float mSweepSpeed;
177 | private float mRotationSpeed;
178 | private int mMinSweepAngle;
179 | private int mMaxSweepAngle;
180 | @CircularProgressDrawable.Style
181 | int mStyle;
182 | private PowerManager mPowerManager;
183 |
184 | public Builder(@NonNull Context context) {
185 | this(context, false);
186 | }
187 |
188 | public Builder(@NonNull Context context, boolean editMode) {
189 | initValues(context, editMode);
190 | }
191 |
192 | private void initValues(@NonNull Context context, boolean editMode) {
193 | mBorderWidth = context.getResources().getDimension(R.dimen.cpb_default_stroke_width);
194 | mSweepSpeed = 1f;
195 | mRotationSpeed = 1f;
196 | if (editMode) {
197 | mColors = new int[]{Color.BLUE};
198 | mMinSweepAngle = 20;
199 | mMaxSweepAngle = 300;
200 | } else {
201 | mColors = new int[]{context.getResources().getColor(R.color.cpb_default_color)};
202 | mMinSweepAngle = context.getResources().getInteger(R.integer.cpb_default_min_sweep_angle);
203 | mMaxSweepAngle = context.getResources().getInteger(R.integer.cpb_default_max_sweep_angle);
204 | }
205 | mStyle = CircularProgressDrawable.STYLE_ROUNDED;
206 | mPowerManager = Utils.powerManager(context);
207 | }
208 |
209 | public Builder color(int color) {
210 | mColors = new int[]{color};
211 | return this;
212 | }
213 |
214 | public Builder colors(int[] colors) {
215 | checkColors(colors);
216 | mColors = colors;
217 | return this;
218 | }
219 |
220 | public Builder sweepSpeed(float sweepSpeed) {
221 | checkSpeed(sweepSpeed);
222 | mSweepSpeed = sweepSpeed;
223 | return this;
224 | }
225 |
226 | public Builder rotationSpeed(float rotationSpeed) {
227 | checkSpeed(rotationSpeed);
228 | mRotationSpeed = rotationSpeed;
229 | return this;
230 | }
231 |
232 | public Builder minSweepAngle(int minSweepAngle) {
233 | checkAngle(minSweepAngle);
234 | mMinSweepAngle = minSweepAngle;
235 | return this;
236 | }
237 |
238 | public Builder maxSweepAngle(int maxSweepAngle) {
239 | checkAngle(maxSweepAngle);
240 | mMaxSweepAngle = maxSweepAngle;
241 | return this;
242 | }
243 |
244 | public Builder strokeWidth(float strokeWidth) {
245 | checkPositiveOrZero(strokeWidth, "StrokeWidth");
246 | mBorderWidth = strokeWidth;
247 | return this;
248 | }
249 |
250 | public Builder style(@CircularProgressDrawable.Style int style) {
251 | mStyle = style;
252 | return this;
253 | }
254 |
255 | public Builder sweepInterpolator(Interpolator interpolator) {
256 | checkNotNull(interpolator, "Sweep interpolator");
257 | mSweepInterpolator = interpolator;
258 | return this;
259 | }
260 |
261 | public Builder angleInterpolator(Interpolator interpolator) {
262 | checkNotNull(interpolator, "Angle interpolator");
263 | mAngleInterpolator = interpolator;
264 | return this;
265 | }
266 |
267 | public CircularProgressDrawable build() {
268 | return new CircularProgressDrawable(
269 | mPowerManager,
270 | new Options(mAngleInterpolator,
271 | mSweepInterpolator,
272 | mBorderWidth,
273 | mColors,
274 | mSweepSpeed,
275 | mRotationSpeed,
276 | mMinSweepAngle,
277 | mMaxSweepAngle,
278 | mStyle));
279 | }
280 | }
281 | }
--------------------------------------------------------------------------------
/library-circular/src/main/java/fr.castorflex.android.circularprogressbar/DefaultDelegate.java:
--------------------------------------------------------------------------------
1 | package fr.castorflex.android.circularprogressbar;
2 |
3 | import android.animation.Animator;
4 | import android.animation.ArgbEvaluator;
5 | import android.animation.ValueAnimator;
6 | import android.graphics.Canvas;
7 | import android.graphics.Paint;
8 | import android.support.annotation.NonNull;
9 | import android.view.animation.Interpolator;
10 | import android.view.animation.LinearInterpolator;
11 |
12 | import static fr.castorflex.android.circularprogressbar.Utils.getAnimatedFraction;
13 |
14 | class DefaultDelegate implements PBDelegate {
15 |
16 | private static final ArgbEvaluator COLOR_EVALUATOR = new ArgbEvaluator();
17 | private static final Interpolator END_INTERPOLATOR = new LinearInterpolator();
18 | private static final long ROTATION_ANIMATOR_DURATION = 2000;
19 | private static final long SWEEP_ANIMATOR_DURATION = 600;
20 | private static final long END_ANIMATOR_DURATION = 200;
21 |
22 | private ValueAnimator mSweepAppearingAnimator;
23 | private ValueAnimator mSweepDisappearingAnimator;
24 | private ValueAnimator mRotationAnimator;
25 | private ValueAnimator mEndAnimator;
26 | private boolean mModeAppearing;
27 |
28 | private int mCurrentColor;
29 | private int mCurrentIndexColor;
30 | private float mCurrentSweepAngle;
31 | private float mCurrentRotationAngleOffset = 0;
32 | private float mCurrentRotationAngle = 0;
33 | private float mCurrentEndRatio = 1f;
34 | private boolean mFirstSweepAnimation;
35 |
36 | //params
37 | private final Interpolator mAngleInterpolator;
38 | private final Interpolator mSweepInterpolator;
39 | private final int[] mColors;
40 | private final float mSweepSpeed;
41 | private final float mRotationSpeed;
42 | private final int mMinSweepAngle;
43 | private final int mMaxSweepAngle;
44 |
45 | private final CircularProgressDrawable mParent;
46 | private CircularProgressDrawable.OnEndListener mOnEndListener;
47 |
48 | DefaultDelegate(@NonNull CircularProgressDrawable parent,
49 | @NonNull Options options) {
50 | mParent = parent;
51 | mSweepInterpolator = options.sweepInterpolator;
52 | mAngleInterpolator = options.angleInterpolator;
53 | mCurrentIndexColor = 0;
54 | mColors = options.colors;
55 | mCurrentColor = mColors[0];
56 | mSweepSpeed = options.sweepSpeed;
57 | mRotationSpeed = options.rotationSpeed;
58 | mMinSweepAngle = options.minSweepAngle;
59 | mMaxSweepAngle = options.maxSweepAngle;
60 |
61 | setupAnimations();
62 | }
63 |
64 | private void reinitValues() {
65 | mFirstSweepAnimation = true;
66 | mCurrentEndRatio = 1f;
67 | mParent.getCurrentPaint().setColor(mCurrentColor);
68 | }
69 |
70 | @Override
71 | public void draw(Canvas canvas, Paint paint) {
72 | float startAngle = mCurrentRotationAngle - mCurrentRotationAngleOffset;
73 | float sweepAngle = mCurrentSweepAngle;
74 | if (!mModeAppearing) {
75 | startAngle = startAngle + (360 - sweepAngle);
76 | }
77 | startAngle %= 360;
78 | if (mCurrentEndRatio < 1f) {
79 | float newSweepAngle = sweepAngle * mCurrentEndRatio;
80 | startAngle = (startAngle + (sweepAngle - newSweepAngle)) % 360;
81 | sweepAngle = newSweepAngle;
82 | }
83 | canvas.drawArc(mParent.getDrawableBounds(), startAngle, sweepAngle, false, paint);
84 | }
85 |
86 | @Override
87 | public void start() {
88 | mEndAnimator.cancel();
89 | reinitValues();
90 | mRotationAnimator.start();
91 | mSweepAppearingAnimator.start();
92 | }
93 |
94 | @Override
95 | public void stop() {
96 | stopAnimators();
97 | }
98 |
99 | private void stopAnimators() {
100 | mRotationAnimator.cancel();
101 | mSweepAppearingAnimator.cancel();
102 | mSweepDisappearingAnimator.cancel();
103 | mEndAnimator.cancel();
104 | }
105 |
106 | private void setAppearing() {
107 | mModeAppearing = true;
108 | mCurrentRotationAngleOffset += mMinSweepAngle;
109 | }
110 |
111 | private void setDisappearing() {
112 | mModeAppearing = false;
113 | mCurrentRotationAngleOffset = mCurrentRotationAngleOffset + (360 - mMaxSweepAngle);
114 | }
115 |
116 | private void setCurrentRotationAngle(float currentRotationAngle) {
117 | mCurrentRotationAngle = currentRotationAngle;
118 | mParent.invalidate();
119 | }
120 |
121 | private void setCurrentSweepAngle(float currentSweepAngle) {
122 | mCurrentSweepAngle = currentSweepAngle;
123 | mParent.invalidate();
124 | }
125 |
126 | private void setEndRatio(float ratio) {
127 | mCurrentEndRatio = ratio;
128 | mParent.invalidate();
129 | }
130 |
131 | //////////////////////////////////////////////////////////////////////////////
132 | //////////////// Animation
133 |
134 | private void setupAnimations() {
135 | mRotationAnimator = ValueAnimator.ofFloat(0f, 360f);
136 | mRotationAnimator.setInterpolator(mAngleInterpolator);
137 | mRotationAnimator.setDuration((long) (ROTATION_ANIMATOR_DURATION / mRotationSpeed));
138 | mRotationAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
139 | @Override
140 | public void onAnimationUpdate(ValueAnimator animation) {
141 | float angle = getAnimatedFraction(animation) * 360f;
142 | setCurrentRotationAngle(angle);
143 | }
144 | });
145 | mRotationAnimator.setRepeatCount(ValueAnimator.INFINITE);
146 | mRotationAnimator.setRepeatMode(ValueAnimator.RESTART);
147 |
148 | mSweepAppearingAnimator = ValueAnimator.ofFloat(mMinSweepAngle, mMaxSweepAngle);
149 | mSweepAppearingAnimator.setInterpolator(mSweepInterpolator);
150 | mSweepAppearingAnimator.setDuration((long) (SWEEP_ANIMATOR_DURATION / mSweepSpeed));
151 | mSweepAppearingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
152 | @Override
153 | public void onAnimationUpdate(ValueAnimator animation) {
154 | float animatedFraction = getAnimatedFraction(animation);
155 | float angle;
156 | if (mFirstSweepAnimation) {
157 | angle = animatedFraction * mMaxSweepAngle;
158 | } else {
159 | angle = mMinSweepAngle + animatedFraction * (mMaxSweepAngle - mMinSweepAngle);
160 | }
161 | setCurrentSweepAngle(angle);
162 | }
163 | });
164 | mSweepAppearingAnimator.addListener(new SimpleAnimatorListener() {
165 | @Override
166 | public void onAnimationStart(Animator animation) {
167 | super.onAnimationStart(animation);
168 | mModeAppearing = true;
169 | }
170 |
171 | @Override
172 | protected void onPreAnimationEnd(Animator animation) {
173 | if (isStartedAndNotCancelled()) {
174 | mFirstSweepAnimation = false;
175 | setDisappearing();
176 | mSweepDisappearingAnimator.start();
177 | }
178 | }
179 | });
180 |
181 | mSweepDisappearingAnimator = ValueAnimator.ofFloat(mMaxSweepAngle, mMinSweepAngle);
182 | mSweepDisappearingAnimator.setInterpolator(mSweepInterpolator);
183 | mSweepDisappearingAnimator.setDuration((long) (SWEEP_ANIMATOR_DURATION / mSweepSpeed));
184 | mSweepDisappearingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
185 | @Override
186 | public void onAnimationUpdate(ValueAnimator animation) {
187 | float animatedFraction = getAnimatedFraction(animation);
188 | setCurrentSweepAngle(mMaxSweepAngle - animatedFraction * (mMaxSweepAngle - mMinSweepAngle));
189 |
190 | long duration = animation.getDuration();
191 | long played = animation.getCurrentPlayTime();
192 | float fraction = (float) played / duration;
193 | if (mColors.length > 1 && fraction > .7f) { //because
194 | int prevColor = mCurrentColor;
195 | int nextColor = mColors[(mCurrentIndexColor + 1) % mColors.length];
196 | int newColor = (Integer) COLOR_EVALUATOR.evaluate((fraction - .7f) / (1 - .7f), prevColor, nextColor);
197 | mParent.getCurrentPaint().setColor(newColor);
198 | }
199 | }
200 | });
201 | mSweepDisappearingAnimator.addListener(new SimpleAnimatorListener() {
202 | @Override
203 | protected void onPreAnimationEnd(Animator animation) {
204 | if (isStartedAndNotCancelled()) {
205 | setAppearing();
206 | mCurrentIndexColor = (mCurrentIndexColor + 1) % mColors.length;
207 | mCurrentColor = mColors[mCurrentIndexColor];
208 | mParent.getCurrentPaint().setColor(mCurrentColor);
209 | mSweepAppearingAnimator.start();
210 | }
211 | }
212 | });
213 | mEndAnimator = ValueAnimator.ofFloat(1f, 0f);
214 | mEndAnimator.setInterpolator(END_INTERPOLATOR);
215 | mEndAnimator.setDuration(END_ANIMATOR_DURATION);
216 | mEndAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
217 | @Override
218 | public void onAnimationUpdate(ValueAnimator animation) {
219 | setEndRatio(1f - getAnimatedFraction(animation));
220 |
221 | }
222 | });
223 | }
224 |
225 | /////////////////////////////////////////////////////////
226 | /// Stop
227 | /////////////////////////////////////////////////////////
228 |
229 | @Override
230 | public void progressiveStop(CircularProgressDrawable.OnEndListener listener) {
231 | if (!mParent.isRunning() || mEndAnimator.isRunning()) {
232 | return;
233 | }
234 | mOnEndListener = listener;
235 | mEndAnimator.addListener(new SimpleAnimatorListener() {
236 |
237 | @Override
238 | public void onPreAnimationEnd(Animator animation) {
239 | mEndAnimator.removeListener(this);
240 | CircularProgressDrawable.OnEndListener endListener = mOnEndListener;
241 | mOnEndListener = null;
242 |
243 | if (isStartedAndNotCancelled()) {
244 | setEndRatio(0f);
245 | mParent.stop();
246 | if (endListener != null) {
247 | endListener.onEnd(mParent);
248 | }
249 | }
250 | }
251 | });
252 | mEndAnimator.start();
253 | }
254 | }
255 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Apache License
2 |
3 | Version 2.0, January 2004
4 |
5 | http://www.apache.org/licenses/
6 |
7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
8 |
9 | 1. Definitions.
10 |
11 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
16 |
17 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
18 |
19 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
20 |
21 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
22 |
23 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
24 |
25 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
26 |
27 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
28 |
29 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
30 |
31 | 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
32 |
33 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
34 |
35 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
36 |
37 | You must give any other recipients of the Work or Derivative Works a copy of this License; and
38 | You must cause any modified files to carry prominent notices stating that You changed the files; and
39 | You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
40 | If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
41 |
42 | You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
43 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
44 |
45 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
46 |
47 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
48 |
49 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
50 |
51 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
52 |
53 | END OF TERMS AND CONDITIONS
54 |
55 | APPENDIX: How to apply the Apache License to your work
56 | To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
57 |
58 | Copyright 2013-2014 Antoine Merle
59 |
60 | Licensed under the Apache License, Version 2.0 (the "License");
61 | you may not use this file except in compliance with the License.
62 | You may obtain a copy of the License at
63 |
64 | http://www.apache.org/licenses/LICENSE-2.0
65 |
66 | Unless required by applicable law or agreed to in writing, software
67 | distributed under the License is distributed on an "AS IS" BASIS,
68 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
69 | See the License for the specific language governing permissions and
70 | limitations under the License.
--------------------------------------------------------------------------------
/sample/src/main/java/fr/castorflex/android/smoothprogressbar/sample/MakeCustomActivity.java:
--------------------------------------------------------------------------------
1 | package fr.castorflex.android.smoothprogressbar.sample;
2 |
3 | import android.app.Activity;
4 | import android.content.res.Resources;
5 | import android.os.Bundle;
6 | import android.util.TypedValue;
7 | import android.view.View;
8 | import android.view.animation.AccelerateDecelerateInterpolator;
9 | import android.view.animation.AccelerateInterpolator;
10 | import android.view.animation.DecelerateInterpolator;
11 | import android.view.animation.Interpolator;
12 | import android.view.animation.LinearInterpolator;
13 | import android.widget.AdapterView;
14 | import android.widget.ArrayAdapter;
15 | import android.widget.CheckBox;
16 | import android.widget.CompoundButton;
17 | import android.widget.SeekBar;
18 | import android.widget.Spinner;
19 | import android.widget.TextView;
20 |
21 | import java.util.Locale;
22 |
23 | import fr.castorflex.android.circularprogressbar.CircularProgressBar;
24 | import fr.castorflex.android.circularprogressbar.CircularProgressDrawable;
25 | import fr.castorflex.android.smoothprogressbar.SmoothProgressBar;
26 |
27 | public class MakeCustomActivity extends Activity {
28 |
29 | private SmoothProgressBar mProgressBar;
30 | private CircularProgressBar mCircularProgressBar;
31 | private CheckBox mCheckBoxMirror;
32 | private CheckBox mCheckBoxReversed;
33 | private CheckBox mCheckBoxGradients;
34 | private Spinner mSpinnerInterpolators;
35 | private SeekBar mSeekBarSectionsCount;
36 | private SeekBar mSeekBarStrokeWidth;
37 | private SeekBar mSeekBarSeparatorLength;
38 | private SeekBar mSeekBarSpeed;
39 | private SeekBar mSeekBarFactor;
40 | private TextView mTextViewFactor;
41 | private TextView mTextViewSpeed;
42 | private TextView mTextViewStrokeWidth;
43 | private TextView mTextViewSeparatorLength;
44 | private TextView mTextViewSectionsCount;
45 |
46 | private Interpolator mCurrentInterpolator;
47 | private int mStrokeWidth = 4;
48 | private int mSeparatorLength;
49 | private int mSectionsCount;
50 | private float mFactor = 1f;
51 | private float mSpeed = 1f;
52 |
53 | @Override
54 | protected void onCreate(Bundle savedInstanceState) {
55 | super.onCreate(savedInstanceState);
56 |
57 | setContentView(R.layout.activity_custom);
58 |
59 | mProgressBar = (SmoothProgressBar) findViewById(R.id.progressbar);
60 | mCircularProgressBar = (CircularProgressBar) findViewById(R.id.progressbar_circular);
61 | mCheckBoxMirror = (CheckBox) findViewById(R.id.checkbox_mirror);
62 | mCheckBoxReversed = (CheckBox) findViewById(R.id.checkbox_reversed);
63 | mCheckBoxGradients = (CheckBox) findViewById(R.id.checkbox_gradients);
64 | mSpinnerInterpolators = (Spinner) findViewById(R.id.spinner_interpolator);
65 | mSeekBarSectionsCount = (SeekBar) findViewById(R.id.seekbar_sections_count);
66 | mSeekBarStrokeWidth = (SeekBar) findViewById(R.id.seekbar_stroke_width);
67 | mSeekBarSeparatorLength = (SeekBar) findViewById(R.id.seekbar_separator_length);
68 | mSeekBarSpeed = (SeekBar) findViewById(R.id.seekbar_speed);
69 | mSeekBarFactor = (SeekBar) findViewById(R.id.seekbar_factor);
70 | mTextViewSpeed = (TextView) findViewById(R.id.textview_speed);
71 | mTextViewSectionsCount = (TextView) findViewById(R.id.textview_sections_count);
72 | mTextViewSeparatorLength = (TextView) findViewById(R.id.textview_separator_length);
73 | mTextViewStrokeWidth = (TextView) findViewById(R.id.textview_stroke_width);
74 | mTextViewFactor = (TextView) findViewById(R.id.textview_factor);
75 |
76 | findViewById(R.id.button_start).setOnClickListener(new View.OnClickListener() {
77 | @Override
78 | public void onClick(View v) {
79 | mProgressBar.progressiveStart();
80 | ((CircularProgressDrawable) mCircularProgressBar.getIndeterminateDrawable()).start();
81 | }
82 | });
83 |
84 | findViewById(R.id.button_stop).setOnClickListener(new View.OnClickListener() {
85 | @Override
86 | public void onClick(View v) {
87 | mProgressBar.progressiveStop();
88 | ((CircularProgressDrawable) mCircularProgressBar.getIndeterminateDrawable()).progressiveStop();
89 | }
90 | });
91 |
92 | mSeekBarFactor.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
93 | @Override
94 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
95 | mFactor = (progress + 1) / 10f;
96 | mTextViewFactor.setText("Factor: " + mFactor);
97 | setInterpolator(mSpinnerInterpolators.getSelectedItemPosition());
98 | }
99 |
100 | @Override
101 | public void onStartTrackingTouch(SeekBar seekBar) {
102 |
103 | }
104 |
105 | @Override
106 | public void onStopTrackingTouch(SeekBar seekBar) {
107 |
108 | }
109 | });
110 |
111 | mSeekBarSpeed.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
112 | @Override
113 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
114 | mSpeed = ((float) progress + 1) / 10;
115 | mTextViewSpeed.setText("Speed: " + mSpeed);
116 | mProgressBar.setSmoothProgressDrawableSpeed(mSpeed);
117 | mProgressBar.setSmoothProgressDrawableProgressiveStartSpeed(mSpeed);
118 | mProgressBar.setSmoothProgressDrawableProgressiveStopSpeed(mSpeed);
119 | updateValues();
120 | }
121 |
122 | @Override
123 | public void onStartTrackingTouch(SeekBar seekBar) {
124 |
125 | }
126 |
127 | @Override
128 | public void onStopTrackingTouch(SeekBar seekBar) {
129 |
130 | }
131 | });
132 |
133 | mSeekBarSectionsCount.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
134 | @Override
135 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
136 | mSectionsCount = progress + 1;
137 | mTextViewSectionsCount.setText("Sections count: " + mSectionsCount);
138 | mProgressBar.setSmoothProgressDrawableSectionsCount(mSectionsCount);
139 | }
140 |
141 | @Override
142 | public void onStartTrackingTouch(SeekBar seekBar) {
143 |
144 | }
145 |
146 | @Override
147 | public void onStopTrackingTouch(SeekBar seekBar) {
148 |
149 | }
150 | });
151 |
152 | mSeekBarSeparatorLength.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
153 | @Override
154 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
155 | mSeparatorLength = progress;
156 | mTextViewSeparatorLength.setText(String.format(Locale.US, "Separator length: %ddp", mSeparatorLength));
157 | mProgressBar.setSmoothProgressDrawableSeparatorLength(dpToPx(mSeparatorLength));
158 | }
159 |
160 | @Override
161 | public void onStartTrackingTouch(SeekBar seekBar) {
162 |
163 | }
164 |
165 | @Override
166 | public void onStopTrackingTouch(SeekBar seekBar) {
167 |
168 | }
169 | });
170 |
171 | mSeekBarStrokeWidth.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
172 | @Override
173 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
174 | mStrokeWidth = progress;
175 | mTextViewStrokeWidth.setText(String.format(Locale.US, "Stroke width: %ddp", mStrokeWidth));
176 | mProgressBar.setSmoothProgressDrawableStrokeWidth(dpToPx(mStrokeWidth));
177 | updateValues();
178 | }
179 |
180 | @Override
181 | public void onStartTrackingTouch(SeekBar seekBar) {
182 |
183 | }
184 |
185 | @Override
186 | public void onStopTrackingTouch(SeekBar seekBar) {
187 |
188 | }
189 | });
190 |
191 | mCheckBoxGradients.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
192 | @Override
193 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
194 | mProgressBar.setSmoothProgressDrawableUseGradients(isChecked);
195 | }
196 | });
197 |
198 | mCheckBoxMirror.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
199 | @Override
200 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
201 | mProgressBar.setSmoothProgressDrawableMirrorMode(isChecked);
202 | }
203 | });
204 |
205 | mCheckBoxReversed.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
206 | @Override
207 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
208 | mProgressBar.setSmoothProgressDrawableReversed(isChecked);
209 | }
210 | });
211 |
212 | mSeekBarSeparatorLength.setProgress(4);
213 | mSeekBarSectionsCount.setProgress(4);
214 | mSeekBarStrokeWidth.setProgress(4);
215 | mSeekBarSpeed.setProgress(9);
216 | mSeekBarFactor.setProgress(9);
217 |
218 | mSpinnerInterpolators.setAdapter(new ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, getResources().getStringArray(R.array.interpolators)));
219 | mSpinnerInterpolators.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
220 |
221 | @Override
222 | public void onItemSelected(AdapterView> parent, View view, int position, long id) {
223 | setInterpolator(position);
224 | }
225 |
226 | @Override
227 | public void onNothingSelected(AdapterView> parent) {
228 |
229 | }
230 | });
231 | mSpinnerInterpolators.setSelection(4);
232 | updateValues();
233 | }
234 |
235 | private void setInterpolator(int position) {
236 | switch (position) {
237 | case 1:
238 | mCurrentInterpolator = new LinearInterpolator();
239 | mSeekBarFactor.setEnabled(false);
240 | break;
241 | case 2:
242 | mCurrentInterpolator = new AccelerateDecelerateInterpolator();
243 | mSeekBarFactor.setEnabled(false);
244 | break;
245 | case 3:
246 | mCurrentInterpolator = new DecelerateInterpolator(mFactor);
247 | mSeekBarFactor.setEnabled(true);
248 | break;
249 | case 4:
250 | mCurrentInterpolator = new FastOutSlowInInterpolator();
251 | mSeekBarFactor.setEnabled(true);
252 | break;
253 | case 0:
254 | default:
255 | mCurrentInterpolator = new AccelerateInterpolator(mFactor);
256 | mSeekBarFactor.setEnabled(true);
257 | break;
258 | }
259 |
260 | mProgressBar.setSmoothProgressDrawableInterpolator(mCurrentInterpolator);
261 | mProgressBar.setSmoothProgressDrawableColors(getResources().getIntArray(R.array.gplus_colors));
262 | updateValues();
263 | }
264 |
265 | private void updateValues() {
266 | CircularProgressDrawable circularProgressDrawable;
267 | CircularProgressDrawable.Builder b = new CircularProgressDrawable.Builder(this)
268 | .colors(getResources().getIntArray(R.array.gplus_colors))
269 | .sweepSpeed(mSpeed)
270 | .rotationSpeed(mSpeed)
271 | .strokeWidth(dpToPx(mStrokeWidth))
272 | .style(CircularProgressDrawable.STYLE_ROUNDED);
273 | if (mCurrentInterpolator != null) {
274 | b.sweepInterpolator(mCurrentInterpolator);
275 | }
276 | mCircularProgressBar.setIndeterminateDrawable(circularProgressDrawable = b.build());
277 |
278 | // /!\ Terrible hack, do not do this at home!
279 | circularProgressDrawable.setBounds(0,
280 | 0,
281 | mCircularProgressBar.getWidth(),
282 | mCircularProgressBar.getHeight());
283 | mCircularProgressBar.setVisibility(View.INVISIBLE);
284 | mCircularProgressBar.setVisibility(View.VISIBLE);
285 | }
286 |
287 | public int dpToPx(int dp) {
288 | Resources r = getResources();
289 | int px = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
290 | dp, r.getDisplayMetrics());
291 | return px;
292 | }
293 | }
294 |
--------------------------------------------------------------------------------
/library/src/main/java/fr/castorflex/android/smoothprogressbar/SmoothProgressBar.java:
--------------------------------------------------------------------------------
1 | package fr.castorflex.android.smoothprogressbar;
2 |
3 | import android.content.Context;
4 | import android.content.res.Resources;
5 | import android.content.res.TypedArray;
6 | import android.graphics.Canvas;
7 | import android.graphics.drawable.Drawable;
8 | import android.util.AttributeSet;
9 | import android.view.animation.AccelerateDecelerateInterpolator;
10 | import android.view.animation.AccelerateInterpolator;
11 | import android.view.animation.DecelerateInterpolator;
12 | import android.view.animation.Interpolator;
13 | import android.view.animation.LinearInterpolator;
14 | import android.widget.ProgressBar;
15 |
16 | /**
17 | * Created by castorflex on 11/10/13.
18 | */
19 | public class SmoothProgressBar extends ProgressBar {
20 |
21 | private static final int INTERPOLATOR_ACCELERATE = 0;
22 | private static final int INTERPOLATOR_LINEAR = 1;
23 | private static final int INTERPOLATOR_ACCELERATEDECELERATE = 2;
24 | private static final int INTERPOLATOR_DECELERATE = 3;
25 |
26 | public SmoothProgressBar(Context context) {
27 | this(context, null);
28 | }
29 |
30 | public SmoothProgressBar(Context context, AttributeSet attrs) {
31 | this(context, attrs, R.attr.spbStyle);
32 | }
33 |
34 | public SmoothProgressBar(Context context, AttributeSet attrs, int defStyle) {
35 | super(context, attrs, defStyle);
36 |
37 | if (isInEditMode()) {
38 | setIndeterminateDrawable(new SmoothProgressDrawable.Builder(context, true).build());
39 | return;
40 | }
41 |
42 | Resources res = context.getResources();
43 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SmoothProgressBar, defStyle, 0);
44 |
45 |
46 | final int color = a.getColor(R.styleable.SmoothProgressBar_spb_color, res.getColor(R.color.spb_default_color));
47 | final int sectionsCount = a.getInteger(R.styleable.SmoothProgressBar_spb_sections_count, res.getInteger(R.integer.spb_default_sections_count));
48 | final int separatorLength = a.getDimensionPixelSize(R.styleable.SmoothProgressBar_spb_stroke_separator_length, res.getDimensionPixelSize(R.dimen.spb_default_stroke_separator_length));
49 | final float strokeWidth = a.getDimension(R.styleable.SmoothProgressBar_spb_stroke_width, res.getDimension(R.dimen.spb_default_stroke_width));
50 | final float speed = a.getFloat(R.styleable.SmoothProgressBar_spb_speed, Float.parseFloat(res.getString(R.string.spb_default_speed)));
51 | final float speedProgressiveStart = a.getFloat(R.styleable.SmoothProgressBar_spb_progressiveStart_speed, speed);
52 | final float speedProgressiveStop = a.getFloat(R.styleable.SmoothProgressBar_spb_progressiveStop_speed, speed);
53 | final int iInterpolator = a.getInteger(R.styleable.SmoothProgressBar_spb_interpolator, -1);
54 | final boolean reversed = a.getBoolean(R.styleable.SmoothProgressBar_spb_reversed, res.getBoolean(R.bool.spb_default_reversed));
55 | final boolean mirrorMode = a.getBoolean(R.styleable.SmoothProgressBar_spb_mirror_mode, res.getBoolean(R.bool.spb_default_mirror_mode));
56 | final int colorsId = a.getResourceId(R.styleable.SmoothProgressBar_spb_colors, 0);
57 | final boolean progressiveStartActivated = a.getBoolean(R.styleable.SmoothProgressBar_spb_progressiveStart_activated, res.getBoolean(R.bool.spb_default_progressiveStart_activated));
58 | final Drawable backgroundDrawable = a.getDrawable(R.styleable.SmoothProgressBar_spb_background);
59 | final boolean generateBackgroundWithColors = a.getBoolean(R.styleable.SmoothProgressBar_spb_generate_background_with_colors, false);
60 | final boolean gradients = a.getBoolean(R.styleable.SmoothProgressBar_spb_gradients, false);
61 | a.recycle();
62 |
63 | //interpolator
64 | Interpolator interpolator = null;
65 | if (iInterpolator == -1) {
66 | interpolator = getInterpolator();
67 | }
68 | if (interpolator == null) {
69 | switch (iInterpolator) {
70 | case INTERPOLATOR_ACCELERATEDECELERATE:
71 | interpolator = new AccelerateDecelerateInterpolator();
72 | break;
73 | case INTERPOLATOR_DECELERATE:
74 | interpolator = new DecelerateInterpolator();
75 | break;
76 | case INTERPOLATOR_LINEAR:
77 | interpolator = new LinearInterpolator();
78 | break;
79 | case INTERPOLATOR_ACCELERATE:
80 | default:
81 | interpolator = new AccelerateInterpolator();
82 | }
83 | }
84 |
85 | int[] colors = null;
86 | //colors
87 | if (colorsId != 0) {
88 | colors = res.getIntArray(colorsId);
89 | }
90 |
91 | SmoothProgressDrawable.Builder builder = new SmoothProgressDrawable.Builder(context)
92 | .speed(speed)
93 | .progressiveStartSpeed(speedProgressiveStart)
94 | .progressiveStopSpeed(speedProgressiveStop)
95 | .interpolator(interpolator)
96 | .sectionsCount(sectionsCount)
97 | .separatorLength(separatorLength)
98 | .strokeWidth(strokeWidth)
99 | .reversed(reversed)
100 | .mirrorMode(mirrorMode)
101 | .progressiveStart(progressiveStartActivated)
102 | .gradients(gradients);
103 |
104 | if (backgroundDrawable != null) {
105 | builder.backgroundDrawable(backgroundDrawable);
106 | }
107 |
108 | if (generateBackgroundWithColors) {
109 | builder.generateBackgroundUsingColors();
110 | }
111 |
112 | if (colors != null && colors.length > 0)
113 | builder.colors(colors);
114 | else
115 | builder.color(color);
116 |
117 | SmoothProgressDrawable d = builder.build();
118 | setIndeterminateDrawable(d);
119 | }
120 |
121 | public void applyStyle(int styleResId) {
122 | TypedArray a = getContext().obtainStyledAttributes(null, R.styleable.SmoothProgressBar, 0, styleResId);
123 |
124 | if (a.hasValue(R.styleable.SmoothProgressBar_spb_color)) {
125 | setSmoothProgressDrawableColor(a.getColor(R.styleable.SmoothProgressBar_spb_color, 0));
126 | }
127 | if (a.hasValue(R.styleable.SmoothProgressBar_spb_colors)) {
128 | int colorsId = a.getResourceId(R.styleable.SmoothProgressBar_spb_colors, 0);
129 | if (colorsId != 0) {
130 | int[] colors = getResources().getIntArray(colorsId);
131 | if (colors != null && colors.length > 0)
132 | setSmoothProgressDrawableColors(colors);
133 | }
134 | }
135 | if (a.hasValue(R.styleable.SmoothProgressBar_spb_sections_count)) {
136 | setSmoothProgressDrawableSectionsCount(a.getInteger(R.styleable.SmoothProgressBar_spb_sections_count, 0));
137 | }
138 | if (a.hasValue(R.styleable.SmoothProgressBar_spb_stroke_separator_length)) {
139 | setSmoothProgressDrawableSeparatorLength(a.getDimensionPixelSize(R.styleable.SmoothProgressBar_spb_stroke_separator_length, 0));
140 | }
141 | if (a.hasValue(R.styleable.SmoothProgressBar_spb_stroke_width)) {
142 | setSmoothProgressDrawableStrokeWidth(a.getDimension(R.styleable.SmoothProgressBar_spb_stroke_width, 0));
143 | }
144 | if (a.hasValue(R.styleable.SmoothProgressBar_spb_speed)) {
145 | setSmoothProgressDrawableSpeed(a.getFloat(R.styleable.SmoothProgressBar_spb_speed, 0));
146 | }
147 | if (a.hasValue(R.styleable.SmoothProgressBar_spb_progressiveStart_speed)) {
148 | setSmoothProgressDrawableProgressiveStartSpeed(a.getFloat(R.styleable.SmoothProgressBar_spb_progressiveStart_speed, 0));
149 | }
150 | if (a.hasValue(R.styleable.SmoothProgressBar_spb_progressiveStop_speed)) {
151 | setSmoothProgressDrawableProgressiveStopSpeed(a.getFloat(R.styleable.SmoothProgressBar_spb_progressiveStop_speed, 0));
152 | }
153 | if (a.hasValue(R.styleable.SmoothProgressBar_spb_reversed)) {
154 | setSmoothProgressDrawableReversed(a.getBoolean(R.styleable.SmoothProgressBar_spb_reversed, false));
155 | }
156 | if (a.hasValue(R.styleable.SmoothProgressBar_spb_mirror_mode)) {
157 | setSmoothProgressDrawableMirrorMode(a.getBoolean(R.styleable.SmoothProgressBar_spb_mirror_mode, false));
158 | }
159 | if (a.hasValue(R.styleable.SmoothProgressBar_spb_progressiveStart_activated)) {
160 | setProgressiveStartActivated(a.getBoolean(R.styleable.SmoothProgressBar_spb_progressiveStart_activated, false));
161 | }
162 | if (a.hasValue(R.styleable.SmoothProgressBar_spb_progressiveStart_activated)) {
163 | setProgressiveStartActivated(a.getBoolean(R.styleable.SmoothProgressBar_spb_progressiveStart_activated, false));
164 | }
165 | if (a.hasValue(R.styleable.SmoothProgressBar_spb_gradients)) {
166 | setSmoothProgressDrawableUseGradients(a.getBoolean(R.styleable.SmoothProgressBar_spb_gradients, false));
167 | }
168 | if (a.hasValue(R.styleable.SmoothProgressBar_spb_generate_background_with_colors)) {
169 | if (a.getBoolean(R.styleable.SmoothProgressBar_spb_generate_background_with_colors, false)) {
170 | setSmoothProgressDrawableBackgroundDrawable(
171 | SmoothProgressBarUtils.generateDrawableWithColors(checkIndeterminateDrawable().getColors(), checkIndeterminateDrawable().getStrokeWidth()));
172 | }
173 | }
174 | if (a.hasValue(R.styleable.SmoothProgressBar_spb_interpolator)) {
175 | int iInterpolator = a.getInteger(R.styleable.SmoothProgressBar_spb_interpolator, -1);
176 | Interpolator interpolator;
177 | switch (iInterpolator) {
178 | case INTERPOLATOR_ACCELERATEDECELERATE:
179 | interpolator = new AccelerateDecelerateInterpolator();
180 | break;
181 | case INTERPOLATOR_DECELERATE:
182 | interpolator = new DecelerateInterpolator();
183 | break;
184 | case INTERPOLATOR_LINEAR:
185 | interpolator = new LinearInterpolator();
186 | break;
187 | case INTERPOLATOR_ACCELERATE:
188 | interpolator = new AccelerateInterpolator();
189 | break;
190 | default:
191 | interpolator = null;
192 | }
193 | if (interpolator != null) {
194 | setInterpolator(interpolator);
195 | }
196 | }
197 | a.recycle();
198 | }
199 |
200 | @Override
201 | protected synchronized void onDraw(Canvas canvas) {
202 | super.onDraw(canvas);
203 | if (isIndeterminate() && getIndeterminateDrawable() instanceof SmoothProgressDrawable &&
204 | !((SmoothProgressDrawable) getIndeterminateDrawable()).isRunning()) {
205 | getIndeterminateDrawable().draw(canvas);
206 | }
207 | }
208 |
209 | private SmoothProgressDrawable checkIndeterminateDrawable() {
210 | Drawable ret = getIndeterminateDrawable();
211 | if (ret == null || !(ret instanceof SmoothProgressDrawable))
212 | throw new RuntimeException("The drawable is not a SmoothProgressDrawable");
213 | return (SmoothProgressDrawable) ret;
214 | }
215 |
216 | @Override
217 | public void setInterpolator(Interpolator interpolator) {
218 | super.setInterpolator(interpolator);
219 | Drawable ret = getIndeterminateDrawable();
220 | if (ret != null && (ret instanceof SmoothProgressDrawable))
221 | ((SmoothProgressDrawable) ret).setInterpolator(interpolator);
222 | }
223 |
224 | public void setSmoothProgressDrawableInterpolator(Interpolator interpolator) {
225 | checkIndeterminateDrawable().setInterpolator(interpolator);
226 | }
227 |
228 | public void setSmoothProgressDrawableColors(int[] colors) {
229 | checkIndeterminateDrawable().setColors(colors);
230 | }
231 |
232 | public void setSmoothProgressDrawableColor(int color) {
233 | checkIndeterminateDrawable().setColor(color);
234 | }
235 |
236 | public void setSmoothProgressDrawableSpeed(float speed) {
237 | checkIndeterminateDrawable().setSpeed(speed);
238 | }
239 |
240 | public void setSmoothProgressDrawableProgressiveStartSpeed(float speed) {
241 | checkIndeterminateDrawable().setProgressiveStartSpeed(speed);
242 | }
243 |
244 | public void setSmoothProgressDrawableProgressiveStopSpeed(float speed) {
245 | checkIndeterminateDrawable().setProgressiveStopSpeed(speed);
246 | }
247 |
248 | public void setSmoothProgressDrawableSectionsCount(int sectionsCount) {
249 | checkIndeterminateDrawable().setSectionsCount(sectionsCount);
250 | }
251 |
252 | public void setSmoothProgressDrawableSeparatorLength(int separatorLength) {
253 | checkIndeterminateDrawable().setSeparatorLength(separatorLength);
254 | }
255 |
256 | public void setSmoothProgressDrawableStrokeWidth(float strokeWidth) {
257 | checkIndeterminateDrawable().setStrokeWidth(strokeWidth);
258 | }
259 |
260 | public void setSmoothProgressDrawableReversed(boolean reversed) {
261 | checkIndeterminateDrawable().setReversed(reversed);
262 | }
263 |
264 | public void setSmoothProgressDrawableMirrorMode(boolean mirrorMode) {
265 | checkIndeterminateDrawable().setMirrorMode(mirrorMode);
266 | }
267 |
268 | public void setProgressiveStartActivated(boolean progressiveStartActivated) {
269 | checkIndeterminateDrawable().setProgressiveStartActivated(progressiveStartActivated);
270 | }
271 |
272 | public void setSmoothProgressDrawableCallbacks(SmoothProgressDrawable.Callbacks listener) {
273 | checkIndeterminateDrawable().setCallbacks(listener);
274 | }
275 |
276 | public void setSmoothProgressDrawableBackgroundDrawable(Drawable drawable) {
277 | checkIndeterminateDrawable().setBackgroundDrawable(drawable);
278 | }
279 |
280 | public void setSmoothProgressDrawableUseGradients(boolean useGradients) {
281 | checkIndeterminateDrawable().setUseGradients(useGradients);
282 | }
283 |
284 | public void progressiveStart() {
285 | checkIndeterminateDrawable().progressiveStart();
286 | }
287 |
288 | public void progressiveStop() {
289 | checkIndeterminateDrawable().progressiveStop();
290 | }
291 | }
292 |
--------------------------------------------------------------------------------
/library/src/main/java/fr/castorflex/android/smoothprogressbar/SmoothProgressDrawable.java:
--------------------------------------------------------------------------------
1 | package fr.castorflex.android.smoothprogressbar;
2 |
3 | import android.content.Context;
4 | import android.content.res.Resources;
5 | import android.graphics.Canvas;
6 | import android.graphics.ColorFilter;
7 | import android.graphics.LinearGradient;
8 | import android.graphics.Paint;
9 | import android.graphics.PixelFormat;
10 | import android.graphics.Rect;
11 | import android.graphics.Shader;
12 | import android.graphics.drawable.Animatable;
13 | import android.graphics.drawable.Drawable;
14 | import android.os.SystemClock;
15 | import android.support.annotation.UiThread;
16 | import android.view.animation.AccelerateInterpolator;
17 | import android.view.animation.Interpolator;
18 |
19 | import java.util.Locale;
20 |
21 | import static fr.castorflex.android.smoothprogressbar.SmoothProgressBarUtils.checkColors;
22 | import static fr.castorflex.android.smoothprogressbar.SmoothProgressBarUtils.checkNotNull;
23 | import static fr.castorflex.android.smoothprogressbar.SmoothProgressBarUtils.checkPositive;
24 | import static fr.castorflex.android.smoothprogressbar.SmoothProgressBarUtils.checkPositiveOrZero;
25 | import static fr.castorflex.android.smoothprogressbar.SmoothProgressBarUtils.checkSpeed;
26 |
27 | /**
28 | * Created by castorflex on 11/10/13.
29 | */
30 | public class SmoothProgressDrawable extends Drawable implements Animatable {
31 |
32 | public interface Callbacks {
33 | public void onStop();
34 |
35 | public void onStart();
36 | }
37 |
38 | private static final long FRAME_DURATION = 1000 / 60;
39 | private final static float OFFSET_PER_FRAME = 0.01f;
40 |
41 | private final Rect fBackgroundRect = new Rect();
42 | private Callbacks mCallbacks;
43 | private Interpolator mInterpolator;
44 | private Rect mBounds;
45 | private Paint mPaint;
46 | private int[] mColors;
47 | private int mColorsIndex;
48 | private boolean mRunning;
49 | private float mCurrentOffset;
50 | private float mFinishingOffset;
51 | private int mSeparatorLength;
52 | private int mSectionsCount;
53 | private float mSpeed;
54 | private float mProgressiveStartSpeed;
55 | private float mProgressiveStopSpeed;
56 | private boolean mReversed;
57 | private boolean mNewTurn;
58 | private boolean mMirrorMode;
59 | private float mMaxOffset;
60 | private boolean mFinishing;
61 | private boolean mProgressiveStartActivated;
62 | private int mStartSection;
63 | private int mCurrentSections;
64 | private float mStrokeWidth;
65 | private Drawable mBackgroundDrawable;
66 | private boolean mUseGradients;
67 | private int[] mLinearGradientColors;
68 | private float[] mLinearGradientPositions;
69 |
70 |
71 | private SmoothProgressDrawable(Interpolator interpolator,
72 | int sectionsCount,
73 | int separatorLength,
74 | int[] colors,
75 | float strokeWidth,
76 | float speed,
77 | float progressiveStartSpeed,
78 | float progressiveStopSpeed,
79 | boolean reversed,
80 | boolean mirrorMode,
81 | Callbacks callbacks,
82 | boolean progressiveStartActivated,
83 | Drawable backgroundDrawable,
84 | boolean useGradients) {
85 | mRunning = false;
86 | mInterpolator = interpolator;
87 | mSectionsCount = sectionsCount;
88 | mStartSection = 0;
89 | mCurrentSections = mSectionsCount;
90 | mSeparatorLength = separatorLength;
91 | mSpeed = speed;
92 | mProgressiveStartSpeed = progressiveStartSpeed;
93 | mProgressiveStopSpeed = progressiveStopSpeed;
94 | mReversed = reversed;
95 | mColors = colors;
96 | mColorsIndex = 0;
97 | mMirrorMode = mirrorMode;
98 | mFinishing = false;
99 | mBackgroundDrawable = backgroundDrawable;
100 | mStrokeWidth = strokeWidth;
101 |
102 | mMaxOffset = 1f / mSectionsCount;
103 |
104 | mPaint = new Paint();
105 | mPaint.setStrokeWidth(strokeWidth);
106 | mPaint.setStyle(Paint.Style.STROKE);
107 | mPaint.setDither(false);
108 | mPaint.setAntiAlias(false);
109 |
110 | mProgressiveStartActivated = progressiveStartActivated;
111 | mCallbacks = callbacks;
112 |
113 | mUseGradients = useGradients;
114 | refreshLinearGradientOptions();
115 | }
116 |
117 | ////////////////////////////////////////////////////////////////////////////
118 | /////////////////// SETTERS
119 |
120 | @UiThread
121 | public void setInterpolator(Interpolator interpolator) {
122 | if (interpolator == null) throw new IllegalArgumentException("Interpolator cannot be null");
123 | mInterpolator = interpolator;
124 | invalidateSelf();
125 | }
126 |
127 | @UiThread
128 | public void setColors(int[] colors) {
129 | if (colors == null || colors.length == 0)
130 | throw new IllegalArgumentException("Colors cannot be null or empty");
131 | mColorsIndex = 0;
132 | mColors = colors;
133 | refreshLinearGradientOptions();
134 | invalidateSelf();
135 | }
136 |
137 | @UiThread
138 | public void setColor(int color) {
139 | setColors(new int[]{color});
140 | }
141 |
142 | @UiThread
143 | public void setSpeed(float speed) {
144 | if (speed < 0) throw new IllegalArgumentException("Speed must be >= 0");
145 | mSpeed = speed;
146 | invalidateSelf();
147 | }
148 |
149 | @UiThread
150 | public void setProgressiveStartSpeed(float speed) {
151 | if (speed < 0) throw new IllegalArgumentException("SpeedProgressiveStart must be >= 0");
152 | mProgressiveStartSpeed = speed;
153 | invalidateSelf();
154 | }
155 |
156 | @UiThread
157 | public void setProgressiveStopSpeed(float speed) {
158 | if (speed < 0) throw new IllegalArgumentException("SpeedProgressiveStop must be >= 0");
159 | mProgressiveStopSpeed = speed;
160 | invalidateSelf();
161 | }
162 |
163 | @UiThread
164 | public void setSectionsCount(int sectionsCount) {
165 | if (sectionsCount <= 0) throw new IllegalArgumentException("SectionsCount must be > 0");
166 | mSectionsCount = sectionsCount;
167 | mMaxOffset = 1f / mSectionsCount;
168 | mCurrentOffset %= mMaxOffset;
169 | refreshLinearGradientOptions();
170 | invalidateSelf();
171 | }
172 |
173 | @UiThread
174 | public void setSeparatorLength(int separatorLength) {
175 | if (separatorLength < 0)
176 | throw new IllegalArgumentException("SeparatorLength must be >= 0");
177 | mSeparatorLength = separatorLength;
178 | invalidateSelf();
179 | }
180 |
181 | @UiThread
182 | public void setStrokeWidth(float strokeWidth) {
183 | if (strokeWidth < 0) throw new IllegalArgumentException("The strokeWidth must be >= 0");
184 | mPaint.setStrokeWidth(strokeWidth);
185 | invalidateSelf();
186 | }
187 |
188 | @UiThread
189 | public void setReversed(boolean reversed) {
190 | if (mReversed == reversed) return;
191 | mReversed = reversed;
192 | invalidateSelf();
193 | }
194 |
195 | @UiThread
196 | public void setMirrorMode(boolean mirrorMode) {
197 | if (mMirrorMode == mirrorMode) return;
198 | mMirrorMode = mirrorMode;
199 | invalidateSelf();
200 | }
201 |
202 | @UiThread
203 | public void setBackgroundDrawable(Drawable backgroundDrawable) {
204 | if (mBackgroundDrawable == backgroundDrawable) return;
205 | mBackgroundDrawable = backgroundDrawable;
206 | invalidateSelf();
207 | }
208 |
209 | public Drawable getBackgroundDrawable() {
210 | return mBackgroundDrawable;
211 | }
212 |
213 | public int[] getColors() {
214 | return mColors;
215 | }
216 |
217 | public float getStrokeWidth() {
218 | return mStrokeWidth;
219 | }
220 |
221 | @UiThread
222 | public void setProgressiveStartActivated(boolean progressiveStartActivated) {
223 | mProgressiveStartActivated = progressiveStartActivated;
224 | }
225 |
226 | @UiThread
227 | public void setUseGradients(boolean useGradients) {
228 | if (mUseGradients == useGradients) return;
229 |
230 | mUseGradients = useGradients;
231 | refreshLinearGradientOptions();
232 | invalidateSelf();
233 | }
234 |
235 | private void refreshLinearGradientOptions() {
236 | if (mUseGradients) {
237 | mLinearGradientColors = new int[mSectionsCount + 2];
238 | mLinearGradientPositions = new float[mSectionsCount + 2];
239 | } else {
240 | mPaint.setShader(null);
241 | mLinearGradientColors = null;
242 | mLinearGradientPositions = null;
243 | }
244 | }
245 |
246 | ////////////////////////////////////////////////////////////////////////////
247 | /////////////////// DRAW
248 |
249 | @Override
250 | public void draw(Canvas canvas) {
251 | mBounds = getBounds();
252 | canvas.clipRect(mBounds);
253 |
254 | //new turn
255 | if (mNewTurn) {
256 | mColorsIndex = decrementColor(mColorsIndex);
257 | mNewTurn = false;
258 |
259 | if (isFinishing()) {
260 | mStartSection++;
261 |
262 | if (mStartSection > mSectionsCount) {
263 | stop();
264 | return;
265 | }
266 | }
267 | if (mCurrentSections < mSectionsCount) {
268 | mCurrentSections++;
269 | }
270 | }
271 |
272 | if (mUseGradients)
273 | prepareGradient();
274 |
275 | drawStrokes(canvas);
276 | }
277 |
278 | @UiThread
279 | private void prepareGradient() {
280 | float xSectionWidth = 1f / mSectionsCount;
281 | int currentIndexColor = mColorsIndex;
282 |
283 | mLinearGradientPositions[0] = 0f;
284 | mLinearGradientPositions[mLinearGradientPositions.length - 1] = 1f;
285 | int firstColorIndex = currentIndexColor - 1;
286 | if (firstColorIndex < 0) firstColorIndex += mColors.length;
287 |
288 | mLinearGradientColors[0] = mColors[firstColorIndex];
289 |
290 | for (int i = 0; i < mSectionsCount; ++i) {
291 |
292 | float position = mInterpolator.getInterpolation(i * xSectionWidth + mCurrentOffset);
293 | mLinearGradientPositions[i + 1] = position;
294 | mLinearGradientColors[i + 1] = mColors[currentIndexColor];
295 |
296 | currentIndexColor = (currentIndexColor + 1) % mColors.length;
297 | }
298 | mLinearGradientColors[mLinearGradientColors.length - 1] = mColors[currentIndexColor];
299 |
300 | float left = mReversed ? (mMirrorMode ? Math.abs(mBounds.left - mBounds.right) / 2 : mBounds.left) : mBounds.left;
301 | float right = mMirrorMode ? (mReversed ? mBounds.left : Math.abs(mBounds.left - mBounds.right) / 2) :
302 | mBounds.right;
303 | float top = mBounds.centerY() - mStrokeWidth / 2;
304 | float bottom = mBounds.centerY() + mStrokeWidth / 2;
305 | LinearGradient linearGradient = new LinearGradient(left, top, right, bottom,
306 | mLinearGradientColors, mLinearGradientPositions,
307 | mMirrorMode ? Shader.TileMode.MIRROR : Shader.TileMode.CLAMP);
308 |
309 | mPaint.setShader(linearGradient);
310 | }
311 |
312 | @UiThread
313 | private void drawStrokes(Canvas canvas) {
314 | if (mReversed) {
315 | canvas.translate(mBounds.width(), 0);
316 | canvas.scale(-1, 1);
317 | }
318 |
319 | float prevValue = 0f;
320 | int boundsWidth = mBounds.width();
321 | if (mMirrorMode) boundsWidth /= 2;
322 | int width = boundsWidth + mSeparatorLength + mSectionsCount;
323 | int centerY = mBounds.centerY();
324 | float xSectionWidth = 1f / mSectionsCount;
325 |
326 | float startX;
327 | float endX;
328 | float firstX = 0;
329 | float lastX = 0;
330 | float prev;
331 | float end;
332 | float spaceLength;
333 | float xOffset;
334 | float ratioSectionWidth;
335 | float sectionWidth;
336 | float drawLength;
337 | int currentIndexColor = mColorsIndex;
338 |
339 | if (mStartSection == mCurrentSections && mCurrentSections == mSectionsCount) {
340 | firstX = canvas.getWidth();
341 | }
342 |
343 | for (int i = 0; i <= mCurrentSections; ++i) {
344 | xOffset = xSectionWidth * i + mCurrentOffset;
345 | prev = Math.max(0f, xOffset - xSectionWidth);
346 | ratioSectionWidth = Math.abs(mInterpolator.getInterpolation(prev) -
347 | mInterpolator.getInterpolation(Math.min(xOffset, 1f)));
348 | sectionWidth = (int) (width * ratioSectionWidth);
349 |
350 | if (sectionWidth + prev < width)
351 | spaceLength = Math.min(sectionWidth, mSeparatorLength);
352 | else
353 | spaceLength = 0f;
354 |
355 | drawLength = sectionWidth > spaceLength ? sectionWidth - spaceLength : 0;
356 | end = prevValue + drawLength;
357 | if (end > prevValue && i >= mStartSection) {
358 | float xFinishingOffset = mInterpolator.getInterpolation(Math.min(mFinishingOffset, 1f));
359 | startX = Math.max(xFinishingOffset * width, Math.min(boundsWidth, prevValue));
360 | endX = Math.min(boundsWidth, end);
361 | drawLine(canvas, boundsWidth, startX, centerY, endX, centerY, currentIndexColor);
362 | if (i == mStartSection) { // first loop
363 | firstX = startX - mSeparatorLength;
364 | }
365 | }
366 | if (i == mCurrentSections) {
367 | lastX = prevValue + sectionWidth; //because we want to keep the separator effect
368 | }
369 |
370 | prevValue = end + spaceLength;
371 | currentIndexColor = incrementColor(currentIndexColor);
372 | }
373 |
374 | drawBackgroundIfNeeded(canvas, firstX, lastX);
375 | }
376 |
377 | @UiThread
378 | private void drawLine(Canvas canvas, int canvasWidth, float startX, float startY, float stopX, float stopY, int currentIndexColor) {
379 | mPaint.setColor(mColors[currentIndexColor]);
380 |
381 | if (!mMirrorMode) {
382 | canvas.drawLine(startX, startY, stopX, stopY, mPaint);
383 | } else {
384 | if (mReversed) {
385 | canvas.drawLine(canvasWidth + startX, startY, canvasWidth + stopX, stopY, mPaint);
386 | canvas.drawLine(canvasWidth - startX, startY, canvasWidth - stopX, stopY, mPaint);
387 | } else {
388 | canvas.drawLine(startX, startY, stopX, stopY, mPaint);
389 | canvas.drawLine(canvasWidth * 2 - startX, startY, canvasWidth * 2 - stopX, stopY, mPaint);
390 | }
391 | }
392 | }
393 |
394 | @UiThread
395 | private void drawBackgroundIfNeeded(Canvas canvas, float firstX, float lastX) {
396 | if (mBackgroundDrawable == null) return;
397 |
398 | fBackgroundRect.top = (int) ((canvas.getHeight() - mStrokeWidth) / 2);
399 | fBackgroundRect.bottom = (int) ((canvas.getHeight() + mStrokeWidth) / 2);
400 |
401 | fBackgroundRect.left = 0;
402 | fBackgroundRect.right = mMirrorMode ? canvas.getWidth() / 2 : canvas.getWidth();
403 | mBackgroundDrawable.setBounds(fBackgroundRect);
404 |
405 | //draw the background if the animation is over
406 | if (!isRunning()) {
407 | if (mMirrorMode) {
408 | canvas.save();
409 | canvas.translate(canvas.getWidth() / 2, 0);
410 | drawBackground(canvas, 0, fBackgroundRect.width());
411 | canvas.scale(-1, 1);
412 | drawBackground(canvas, 0, fBackgroundRect.width());
413 | canvas.restore();
414 | } else {
415 | drawBackground(canvas, 0, fBackgroundRect.width());
416 | }
417 | return;
418 | }
419 |
420 | if (!isFinishing() && !isStarting()) return;
421 |
422 | if (firstX > lastX) {
423 | float temp = firstX;
424 | firstX = lastX;
425 | lastX = temp;
426 | }
427 |
428 | if (firstX > 0) {
429 | if (mMirrorMode) {
430 | canvas.save();
431 | canvas.translate(canvas.getWidth() / 2, 0);
432 | if (mReversed) {
433 | drawBackground(canvas, 0, firstX);
434 | canvas.scale(-1, 1);
435 | drawBackground(canvas, 0, firstX);
436 | } else {
437 | drawBackground(canvas, canvas.getWidth() / 2 - firstX, canvas.getWidth() / 2);
438 | canvas.scale(-1, 1);
439 | drawBackground(canvas, canvas.getWidth() / 2 - firstX, canvas.getWidth() / 2);
440 | }
441 | canvas.restore();
442 | } else {
443 | drawBackground(canvas, 0, firstX);
444 | }
445 | }
446 | if (lastX <= canvas.getWidth()) {
447 | if (mMirrorMode) {
448 | canvas.save();
449 | canvas.translate(canvas.getWidth() / 2, 0);
450 | if (mReversed) {
451 | drawBackground(canvas, lastX, canvas.getWidth() / 2);
452 | canvas.scale(-1, 1);
453 | drawBackground(canvas, lastX, canvas.getWidth() / 2);
454 | } else {
455 | drawBackground(canvas, 0, canvas.getWidth() / 2 - lastX);
456 | canvas.scale(-1, 1);
457 | drawBackground(canvas, 0, canvas.getWidth() / 2 - lastX);
458 | }
459 | canvas.restore();
460 | } else {
461 | drawBackground(canvas, lastX, canvas.getWidth());
462 | }
463 | }
464 | }
465 |
466 | @UiThread
467 | private void drawBackground(Canvas canvas, float fromX, float toX) {
468 | int count = canvas.save();
469 | canvas.clipRect(fromX, (int) ((canvas.getHeight() - mStrokeWidth) / 2),
470 | toX, (int) ((canvas.getHeight() + mStrokeWidth) / 2));
471 | mBackgroundDrawable.draw(canvas);
472 | canvas.restoreToCount(count);
473 | }
474 |
475 | @UiThread
476 | private int incrementColor(int colorIndex) {
477 | ++colorIndex;
478 | if (colorIndex >= mColors.length) colorIndex = 0;
479 | return colorIndex;
480 | }
481 |
482 | @UiThread
483 | private int decrementColor(int colorIndex) {
484 | --colorIndex;
485 | if (colorIndex < 0) colorIndex = mColors.length - 1;
486 | return colorIndex;
487 | }
488 |
489 | /**
490 | * Start the animation with the first color.
491 | * Calls progressiveStart(0)
492 | */
493 | @UiThread
494 | public void progressiveStart() {
495 | progressiveStart(0);
496 | }
497 |
498 | /**
499 | * Start the animation from a given color.
500 | *
501 | * @param index
502 | */
503 | @UiThread
504 | public void progressiveStart(int index) {
505 | resetProgressiveStart(index);
506 | start();
507 | }
508 |
509 | @UiThread
510 | private void resetProgressiveStart(int index) {
511 | checkColorIndex(index);
512 |
513 | mCurrentOffset = 0;
514 | mFinishing = false;
515 | mFinishingOffset = 0f;
516 | mStartSection = 0;
517 | mCurrentSections = 0;
518 | mColorsIndex = index;
519 | }
520 |
521 | /**
522 | * Finish the animation by animating the remaining sections.
523 | */
524 | @UiThread
525 | public void progressiveStop() {
526 | mFinishing = true;
527 | mStartSection = 0;
528 | }
529 |
530 | @Override
531 | public void setAlpha(int alpha) {
532 | mPaint.setAlpha(alpha);
533 | }
534 |
535 | @Override
536 | public void setColorFilter(ColorFilter cf) {
537 | mPaint.setColorFilter(cf);
538 | }
539 |
540 | @Override
541 | public int getOpacity() {
542 | return PixelFormat.TRANSPARENT;
543 | }
544 |
545 | ///////////////////////////////////////////////////////////////////////////
546 | /////////////////// Animation: based on http://cyrilmottier.com/2012/11/27/actionbar-on-the-move/
547 | @Override
548 | public void start() {
549 | if (mProgressiveStartActivated) {
550 | resetProgressiveStart(0);
551 | }
552 | if (isRunning()) return;
553 | if (mCallbacks != null) {
554 | mCallbacks.onStart();
555 | }
556 | scheduleSelf(mUpdater, SystemClock.uptimeMillis() + FRAME_DURATION);
557 | invalidateSelf();
558 | }
559 |
560 | @Override
561 | public void stop() {
562 | if (!isRunning()) return;
563 | if (mCallbacks != null) {
564 | mCallbacks.onStop();
565 | }
566 | mRunning = false;
567 | unscheduleSelf(mUpdater);
568 | }
569 |
570 | @Override
571 | public void scheduleSelf(Runnable what, long when) {
572 | mRunning = true;
573 | super.scheduleSelf(what, when);
574 | }
575 |
576 | @Override
577 | public boolean isRunning() {
578 | return mRunning;
579 | }
580 |
581 | public boolean isStarting() {
582 | return mCurrentSections < mSectionsCount;
583 | }
584 |
585 | public boolean isFinishing() {
586 | return mFinishing;
587 | }
588 |
589 | private final Runnable mUpdater = new Runnable() {
590 |
591 | @Override
592 | public void run() {
593 | if (isFinishing()) {
594 | mFinishingOffset += (OFFSET_PER_FRAME * mProgressiveStopSpeed);
595 | mCurrentOffset += (OFFSET_PER_FRAME * mProgressiveStopSpeed);
596 | if (mFinishingOffset >= 1f) {
597 | stop();
598 | }
599 | } else if (isStarting()) {
600 | mCurrentOffset += (OFFSET_PER_FRAME * mProgressiveStartSpeed);
601 | } else {
602 | mCurrentOffset += (OFFSET_PER_FRAME * mSpeed);
603 | }
604 |
605 | if (mCurrentOffset >= mMaxOffset) {
606 | mNewTurn = true;
607 | mCurrentOffset -= mMaxOffset;
608 | }
609 |
610 | if (isRunning())
611 | scheduleSelf(mUpdater, SystemClock.uptimeMillis() + FRAME_DURATION);
612 |
613 | invalidateSelf();
614 | }
615 | };
616 |
617 | ////////////////////////////////////////////////////////////////////////////
618 | /////////////////// Listener
619 |
620 | public void setCallbacks(Callbacks callbacks) {
621 | mCallbacks = callbacks;
622 | }
623 |
624 | ////////////////////////////////////////////////////////////////////////////
625 | /////////////////// Checks
626 |
627 | private void checkColorIndex(int index) {
628 | if (index < 0 || index >= mColors.length) {
629 | throw new IllegalArgumentException(String.format(Locale.US, "Index %d not valid", index));
630 | }
631 | }
632 |
633 | ////////////////////////////////////////////////////////////////////////////
634 | /////////////////// BUILDER
635 |
636 | /**
637 | * Builder for SmoothProgressDrawable! You must use it!
638 | */
639 | public static class Builder {
640 | private Interpolator mInterpolator;
641 | private int mSectionsCount;
642 | private int[] mColors;
643 | private float mSpeed;
644 | private float mProgressiveStartSpeed;
645 | private float mProgressiveStopSpeed;
646 | private boolean mReversed;
647 | private boolean mMirrorMode;
648 | private float mStrokeWidth;
649 | private int mStrokeSeparatorLength;
650 | private boolean mProgressiveStartActivated;
651 | private boolean mGenerateBackgroundUsingColors;
652 | private boolean mGradients;
653 | private Drawable mBackgroundDrawableWhenHidden;
654 |
655 | private Callbacks mOnProgressiveStopEndedListener;
656 |
657 | public Builder(Context context) {
658 | this(context, false);
659 | }
660 |
661 | public Builder(Context context, boolean editMode) {
662 | initValues(context, editMode);
663 | }
664 |
665 | public SmoothProgressDrawable build() {
666 | if (mGenerateBackgroundUsingColors) {
667 | mBackgroundDrawableWhenHidden = SmoothProgressBarUtils.generateDrawableWithColors(mColors, mStrokeWidth);
668 | }
669 | SmoothProgressDrawable ret = new SmoothProgressDrawable(
670 | mInterpolator,
671 | mSectionsCount,
672 | mStrokeSeparatorLength,
673 | mColors,
674 | mStrokeWidth,
675 | mSpeed,
676 | mProgressiveStartSpeed,
677 | mProgressiveStopSpeed,
678 | mReversed,
679 | mMirrorMode,
680 | mOnProgressiveStopEndedListener,
681 | mProgressiveStartActivated,
682 | mBackgroundDrawableWhenHidden,
683 | mGradients);
684 | return ret;
685 | }
686 |
687 | private void initValues(Context context, boolean editMode) {
688 | Resources res = context.getResources();
689 | mInterpolator = new AccelerateInterpolator();
690 | if (!editMode) {
691 | mSectionsCount = res.getInteger(R.integer.spb_default_sections_count);
692 | mSpeed = Float.parseFloat(res.getString(R.string.spb_default_speed));
693 | mReversed = res.getBoolean(R.bool.spb_default_reversed);
694 | mProgressiveStartActivated = res.getBoolean(R.bool.spb_default_progressiveStart_activated);
695 | mColors = new int[]{res.getColor(R.color.spb_default_color)};
696 | mStrokeSeparatorLength = res.getDimensionPixelSize(R.dimen.spb_default_stroke_separator_length);
697 | mStrokeWidth = res.getDimensionPixelOffset(R.dimen.spb_default_stroke_width);
698 | } else {
699 | mSectionsCount = 4;
700 | mSpeed = 1f;
701 | mReversed = false;
702 | mProgressiveStartActivated = false;
703 | mColors = new int[]{0xff33b5e5};
704 | mStrokeSeparatorLength = 4;
705 | mStrokeWidth = 4;
706 | }
707 | mProgressiveStartSpeed = mSpeed;
708 | mProgressiveStopSpeed = mSpeed;
709 | mGradients = false;
710 | }
711 |
712 | public Builder interpolator(Interpolator interpolator) {
713 | checkNotNull(interpolator, "Interpolator");
714 | mInterpolator = interpolator;
715 | return this;
716 | }
717 |
718 | public Builder sectionsCount(int sectionsCount) {
719 | checkPositive(sectionsCount, "Sections count");
720 | mSectionsCount = sectionsCount;
721 | return this;
722 | }
723 |
724 | public Builder separatorLength(int separatorLength) {
725 | checkPositiveOrZero(separatorLength, "Separator length");
726 | mStrokeSeparatorLength = separatorLength;
727 | return this;
728 | }
729 |
730 | public Builder color(int color) {
731 | mColors = new int[]{color};
732 | return this;
733 | }
734 |
735 | public Builder colors(int[] colors) {
736 | checkColors(colors);
737 | mColors = colors;
738 | return this;
739 | }
740 |
741 | public Builder strokeWidth(float width) {
742 | checkPositiveOrZero(width, "Width");
743 | mStrokeWidth = width;
744 | return this;
745 | }
746 |
747 | public Builder speed(float speed) {
748 | checkSpeed(speed);
749 | mSpeed = speed;
750 | return this;
751 | }
752 |
753 | public Builder progressiveStartSpeed(float progressiveStartSpeed) {
754 | checkSpeed(progressiveStartSpeed);
755 | mProgressiveStartSpeed = progressiveStartSpeed;
756 | return this;
757 | }
758 |
759 | public Builder progressiveStopSpeed(float progressiveStopSpeed) {
760 | checkSpeed(progressiveStopSpeed);
761 | mProgressiveStopSpeed = progressiveStopSpeed;
762 | return this;
763 | }
764 |
765 | public Builder reversed(boolean reversed) {
766 | mReversed = reversed;
767 | return this;
768 | }
769 |
770 | public Builder mirrorMode(boolean mirrorMode) {
771 | mMirrorMode = mirrorMode;
772 | return this;
773 | }
774 |
775 | public Builder progressiveStart(boolean progressiveStartActivated) {
776 | mProgressiveStartActivated = progressiveStartActivated;
777 | return this;
778 | }
779 |
780 | public Builder callbacks(Callbacks onProgressiveStopEndedListener) {
781 | mOnProgressiveStopEndedListener = onProgressiveStopEndedListener;
782 | return this;
783 | }
784 |
785 | public Builder backgroundDrawable(Drawable backgroundDrawableWhenHidden) {
786 | mBackgroundDrawableWhenHidden = backgroundDrawableWhenHidden;
787 | return this;
788 | }
789 |
790 | public Builder generateBackgroundUsingColors() {
791 | mGenerateBackgroundUsingColors = true;
792 | return this;
793 | }
794 |
795 | public Builder gradients() {
796 | return gradients(true);
797 | }
798 |
799 | public Builder gradients(boolean useGradients) {
800 | mGradients = useGradients;
801 | return this;
802 | }
803 | }
804 | }
805 |
--------------------------------------------------------------------------------