summaryrefslogtreecommitdiffstats
path: root/sparkbutton
diff options
context:
space:
mode:
authorThomas <tschneider.ac@gmail.com>2023-01-13 16:19:50 +0100
committerThomas <tschneider.ac@gmail.com>2023-01-13 16:19:50 +0100
commit107ac13e1547c50424497f29116d1aaf9bc6ab69 (patch)
treee7f37e97b66858ef8e42183937fc22ec16daa027 /sparkbutton
parentd70c285beaf058f8817dffc04b980f9c3cb93e31 (diff)
Fix issue #745 - Update library for bottom buttons.
Diffstat (limited to 'sparkbutton')
-rw-r--r--sparkbutton/.gitignore1
-rw-r--r--sparkbutton/build.gradle27
-rw-r--r--sparkbutton/gradle.properties3
-rw-r--r--sparkbutton/maven-push.gradle96
-rw-r--r--sparkbutton/proguard-rules.pro17
-rw-r--r--sparkbutton/src/main/AndroidManifest.xml4
-rw-r--r--sparkbutton/src/main/java/com/varunest/sparkbutton/SparkButton.java313
-rw-r--r--sparkbutton/src/main/java/com/varunest/sparkbutton/SparkButtonBuilder.java61
-rw-r--r--sparkbutton/src/main/java/com/varunest/sparkbutton/SparkEventListener.java10
-rw-r--r--sparkbutton/src/main/java/com/varunest/sparkbutton/helpers/SparkAnimationView.java249
-rw-r--r--sparkbutton/src/main/java/com/varunest/sparkbutton/helpers/Utils.java30
-rw-r--r--sparkbutton/src/main/res/values/attrs.xml11
-rw-r--r--sparkbutton/src/main/res/values/colors.xml6
-rw-r--r--sparkbutton/src/main/res/values/strings.xml3
14 files changed, 831 insertions, 0 deletions
diff --git a/sparkbutton/.gitignore b/sparkbutton/.gitignore
new file mode 100644
index 000000000..796b96d1c
--- /dev/null
+++ b/sparkbutton/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/sparkbutton/build.gradle b/sparkbutton/build.gradle
new file mode 100644
index 000000000..54d8db901
--- /dev/null
+++ b/sparkbutton/build.gradle
@@ -0,0 +1,27 @@
+apply plugin: 'com.android.library'
+
+group = 'com.github.tom79'
+
+android {
+ compileSdkVersion 33
+
+ defaultConfig {
+ minSdkVersion 15
+ targetSdkVersion 33
+ versionCode 3
+ versionName "1.0.12"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+ lintOptions {
+ abortOnError false
+ }
+}
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation 'androidx.appcompat:appcompat:1.6.0'
+}
diff --git a/sparkbutton/gradle.properties b/sparkbutton/gradle.properties
new file mode 100644
index 000000000..89a05eff9
--- /dev/null
+++ b/sparkbutton/gradle.properties
@@ -0,0 +1,3 @@
+POM_NAME=SparkButton
+POM_ARTIFACT_ID=sparkbutton
+POM_PACKAGING=aar \ No newline at end of file
diff --git a/sparkbutton/maven-push.gradle b/sparkbutton/maven-push.gradle
new file mode 100644
index 000000000..8ebb6959b
--- /dev/null
+++ b/sparkbutton/maven-push.gradle
@@ -0,0 +1,96 @@
+apply plugin: 'maven'
+apply plugin: 'signing'
+
+def isReleaseBuild() {
+ return VERSION_NAME.contains("SNAPSHOT") == false
+}
+
+def getReleaseRepositoryUrl() {
+ return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL
+ : "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
+}
+
+def getSnapshotRepositoryUrl() {
+ return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL
+ : "https://oss.sonatype.org/content/repositories/snapshots/"
+}
+
+def getRepositoryUsername() {
+ return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : ""
+}
+
+def getRepositoryPassword() {
+ return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : ""
+}
+
+afterEvaluate { project ->
+ uploadArchives {
+ repositories {
+ mavenDeployer {
+ beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
+
+ pom.groupId = GROUP
+ pom.artifactId = POM_ARTIFACT_ID
+ pom.version = VERSION_NAME
+
+ repository(url: getReleaseRepositoryUrl()) {
+ authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
+ }
+ snapshotRepository(url: getSnapshotRepositoryUrl()) {
+ authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
+ }
+
+ pom.project {
+ name POM_NAME
+ packaging POM_PACKAGING
+ description POM_DESCRIPTION
+ url POM_URL
+
+ scm {
+ url POM_SCM_URL
+ connection POM_SCM_CONNECTION
+ developerConnection POM_SCM_DEV_CONNECTION
+ }
+
+ licenses {
+ license {
+ name POM_LICENCE_NAME
+ url POM_LICENCE_URL
+ distribution POM_LICENCE_DIST
+ }
+ }
+
+ developers {
+ developer {
+ id POM_DEVELOPER_ID
+ name POM_DEVELOPER_NAME
+ }
+ }
+ }
+ }
+ }
+ }
+
+ signing {
+ required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") }
+ sign configurations.archives
+ }
+
+ //task androidJavadocs(type: Javadoc) {
+ //source = android.sourceSets.main.allJava
+ //}
+
+ //task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
+ //classifier = 'javadoc'
+ //from androidJavadocs.destinationDir
+ //}
+
+ task androidSourcesJar(type: Jar) {
+ classifier = 'sources'
+ from android.sourceSets.main.java.sourceFiles
+ }
+
+ artifacts {
+ archives androidSourcesJar
+ }
+} \ No newline at end of file
diff --git a/sparkbutton/proguard-rules.pro b/sparkbutton/proguard-rules.pro
new file mode 100644
index 000000000..5407790ba
--- /dev/null
+++ b/sparkbutton/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /android-sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/sparkbutton/src/main/AndroidManifest.xml b/sparkbutton/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..1f76ffe6a
--- /dev/null
+++ b/sparkbutton/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+<manifest package="com.varunest.sparkbutton">
+
+ <application />
+</manifest>
diff --git a/sparkbutton/src/main/java/com/varunest/sparkbutton/SparkButton.java b/sparkbutton/src/main/java/com/varunest/sparkbutton/SparkButton.java
new file mode 100644
index 000000000..19787c5b8
--- /dev/null
+++ b/sparkbutton/src/main/java/com/varunest/sparkbutton/SparkButton.java
@@ -0,0 +1,313 @@
+package com.varunest.sparkbutton;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.OvershootInterpolator;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.DrawableRes;
+import androidx.annotation.Px;
+import androidx.appcompat.widget.AppCompatImageView;
+import androidx.core.content.ContextCompat;
+
+import com.varunest.sparkbutton.helpers.SparkAnimationView;
+import com.varunest.sparkbutton.helpers.Utils;
+
+/**
+ * @author varun 7th July 2016
+ */
+public class SparkButton extends FrameLayout implements View.OnClickListener {
+ private static final DecelerateInterpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator();
+ private static final AccelerateDecelerateInterpolator ACCELERATE_DECELERATE_INTERPOLATOR = new AccelerateDecelerateInterpolator();
+ private static final OvershootInterpolator OVERSHOOT_INTERPOLATOR = new OvershootInterpolator(4);
+
+ private static final int INVALID_RESOURCE_ID = -1;
+ private static final float ANIMATIONVIEW_SIZE_FACTOR = 3;
+ private static final float DOTS_SIZE_FACTOR = .08f;
+ int activeImageTint;
+ int inActiveImageTint;
+ private @DrawableRes
+ int imageResourceIdActive = INVALID_RESOURCE_ID;
+ private @DrawableRes
+ int imageResourceIdInactive = INVALID_RESOURCE_ID;
+ private @Px
+ int imageSize;
+ private @ColorInt
+ int primaryColor;
+ private @ColorInt
+ int secondaryColor;
+ private SparkAnimationView sparkAnimationView;
+ private ImageView imageView;
+ private float animationSpeed = 1;
+ private boolean isChecked = false;
+ private AnimatorSet animatorSet;
+ private SparkEventListener listener;
+
+ SparkButton(Context context) {
+ super(context);
+ }
+
+ public SparkButton(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initFromXML(attrs);
+ init();
+ }
+
+ public SparkButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ initFromXML(attrs);
+ init();
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public SparkButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ initFromXML(attrs);
+ init();
+ }
+
+
+ void init() {
+ int animationViewSize = (int) (imageSize * ANIMATIONVIEW_SIZE_FACTOR);
+
+ sparkAnimationView = new SparkAnimationView(getContext());
+ LayoutParams dotsViewLayoutParams = new LayoutParams(animationViewSize, animationViewSize, Gravity.CENTER);
+ sparkAnimationView.setLayoutParams(dotsViewLayoutParams);
+
+ sparkAnimationView.setColors(secondaryColor, primaryColor);
+ sparkAnimationView.setMaxDotSize((int) (imageSize * DOTS_SIZE_FACTOR));
+
+ addView(sparkAnimationView);
+
+ imageView = new AppCompatImageView(getContext());
+ LayoutParams imageViewLayoutParams = new LayoutParams(imageSize, imageSize, Gravity.CENTER);
+ imageView.setLayoutParams(imageViewLayoutParams);
+
+ addView(imageView);
+
+ if (imageResourceIdInactive != INVALID_RESOURCE_ID) {
+ // should load inactive img first
+ imageView.setImageResource(imageResourceIdInactive);
+ } else if (imageResourceIdActive != INVALID_RESOURCE_ID) {
+ imageView.setImageResource(imageResourceIdActive);
+ } else {
+ throw new IllegalArgumentException("One of Inactive/Active Image Resources is required!");
+ }
+ setOnTouchListener();
+ setOnClickListener(this);
+ }
+
+ /**
+ * Call this function to start spark animation
+ */
+ public void playAnimation() {
+ if (animatorSet != null) {
+ animatorSet.cancel();
+ }
+
+ imageView.animate().cancel();
+ imageView.setScaleX(0);
+ imageView.setScaleY(0);
+ sparkAnimationView.setInnerCircleRadiusProgress(0);
+ sparkAnimationView.setOuterCircleRadiusProgress(0);
+ sparkAnimationView.setCurrentProgress(0);
+
+ animatorSet = new AnimatorSet();
+
+ ObjectAnimator outerCircleAnimator = ObjectAnimator.ofFloat(sparkAnimationView, SparkAnimationView.OUTER_CIRCLE_RADIUS_PROGRESS, 0.1f, 1f);
+ outerCircleAnimator.setDuration((long) (250 / animationSpeed));
+ outerCircleAnimator.setInterpolator(DECELERATE_INTERPOLATOR);
+
+ ObjectAnimator innerCircleAnimator = ObjectAnimator.ofFloat(sparkAnimationView, SparkAnimationView.INNER_CIRCLE_RADIUS_PROGRESS, 0.1f, 1f);
+ innerCircleAnimator.setDuration((long) (200 / animationSpeed));
+ innerCircleAnimator.setStartDelay((long) (200 / animationSpeed));
+ innerCircleAnimator.setInterpolator(DECELERATE_INTERPOLATOR);
+
+ ObjectAnimator starScaleYAnimator = ObjectAnimator.ofFloat(imageView, ImageView.SCALE_Y, 0.2f, 1f);
+ starScaleYAnimator.setDuration((long) (350 / animationSpeed));
+ starScaleYAnimator.setStartDelay((long) (250 / animationSpeed));
+ starScaleYAnimator.setInterpolator(OVERSHOOT_INTERPOLATOR);
+
+ ObjectAnimator starScaleXAnimator = ObjectAnimator.ofFloat(imageView, ImageView.SCALE_X, 0.2f, 1f);
+ starScaleXAnimator.setDuration((long) (350 / animationSpeed));
+ starScaleXAnimator.setStartDelay((long) (250 / animationSpeed));
+ starScaleXAnimator.setInterpolator(OVERSHOOT_INTERPOLATOR);
+
+ ObjectAnimator dotsAnimator = ObjectAnimator.ofFloat(sparkAnimationView, SparkAnimationView.DOTS_PROGRESS, 0, 1f);
+ dotsAnimator.setDuration((long) (900 / animationSpeed));
+ dotsAnimator.setStartDelay((long) (50 / animationSpeed));
+ dotsAnimator.setInterpolator(ACCELERATE_DECELERATE_INTERPOLATOR);
+
+ animatorSet.playTogether(
+ outerCircleAnimator,
+ innerCircleAnimator,
+ starScaleYAnimator,
+ starScaleXAnimator,
+ dotsAnimator
+ );
+
+ animatorSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ sparkAnimationView.setInnerCircleRadiusProgress(0);
+ sparkAnimationView.setOuterCircleRadiusProgress(0);
+ sparkAnimationView.setCurrentProgress(0);
+ imageView.setScaleX(1);
+ imageView.setScaleY(1);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ }
+ });
+
+ animatorSet.start();
+ }
+
+
+ public @Px
+ int getImageSize() {
+ return imageSize;
+ }
+
+ public void setImageSize(@Px int imageSize) {
+ this.imageSize = imageSize;
+ }
+
+ public @ColorInt
+ int getPrimaryColor() {
+ return primaryColor;
+ }
+
+ public void setPrimaryColor(@ColorInt int primaryColor) {
+ this.primaryColor = primaryColor;
+ }
+
+ public @ColorInt
+ int getSecondaryColor() {
+ return secondaryColor;
+ }
+
+ public void setSecondaryColor(@ColorInt int secondaryColor) {
+ this.secondaryColor = secondaryColor;
+ }
+
+ public void setAnimationSpeed(float animationSpeed) {
+ this.animationSpeed = animationSpeed;
+ }
+
+ /**
+ * @return Returns whether the button is checked (Active) or not.
+ */
+ public boolean isChecked() {
+ return isChecked;
+ }
+
+ /**
+ * Change Button State (Works only if both active and disabled image resource is defined)
+ *
+ * @param flag desired checked state of the button
+ */
+ public void setChecked(boolean flag) {
+ isChecked = flag;
+ imageView.setImageResource(isChecked ? imageResourceIdActive : imageResourceIdInactive);
+ }
+
+ public void setInactiveImage(int inactiveResource) {
+ this.imageResourceIdInactive = inactiveResource;
+ imageView.setImageResource(isChecked ? imageResourceIdActive : imageResourceIdInactive);
+ }
+
+ public void setActiveImage(int activeResource) {
+ this.imageResourceIdActive = activeResource;
+ imageView.setImageResource(isChecked ? imageResourceIdActive : imageResourceIdInactive);
+ }
+
+ @Override
+ public void onClick(View v) {
+ boolean shouldPlayAnimation = listener == null || listener.onEvent(this, isChecked);
+
+ if (shouldPlayAnimation) {
+ if (imageResourceIdInactive != INVALID_RESOURCE_ID) {
+ isChecked = !isChecked;
+
+ imageView.setImageResource(isChecked ? imageResourceIdActive : imageResourceIdInactive);
+
+ if (animatorSet != null) {
+ animatorSet.cancel();
+ }
+ if (isChecked) {
+ sparkAnimationView.setVisibility(VISIBLE);
+ playAnimation();
+ } else {
+ sparkAnimationView.setVisibility(INVISIBLE);
+ }
+ } else {
+ playAnimation();
+ }
+ }
+ }
+
+
+ private void setOnTouchListener() {
+ setOnTouchListener((v, event) -> {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ imageView.animate().scaleX(0.8f).scaleY(0.8f).setDuration(150).setInterpolator(DECELERATE_INTERPOLATOR);
+ setPressed(true);
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ break;
+
+ case MotionEvent.ACTION_UP:
+ imageView.animate().scaleX(1).scaleY(1).setInterpolator(DECELERATE_INTERPOLATOR);
+ if (isPressed()) {
+ performClick();
+ setPressed(false);
+ }
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ imageView.animate().scaleX(1).scaleY(1).setInterpolator(DECELERATE_INTERPOLATOR);
+ break;
+ }
+ return true;
+ });
+ }
+
+ private int getColor(int id) {
+ return ContextCompat.getColor(getContext(), id);
+ }
+
+
+ private void initFromXML(AttributeSet attr) {
+ TypedArray a = getContext().obtainStyledAttributes(attr, R.styleable.SparkButton);
+ imageSize = a.getDimensionPixelOffset(R.styleable.SparkButton_iconSize, Utils.dpToPx(getContext(), 50));
+ imageResourceIdActive = a.getResourceId(R.styleable.SparkButton_activeImage, INVALID_RESOURCE_ID);
+ imageResourceIdInactive = a.getResourceId(R.styleable.SparkButton_inactiveImage, INVALID_RESOURCE_ID);
+ primaryColor = ContextCompat.getColor(getContext(), a.getResourceId(R.styleable.SparkButton_primaryColor, R.color.spark_primary_color));
+ secondaryColor = ContextCompat.getColor(getContext(), a.getResourceId(R.styleable.SparkButton_secondaryColor, R.color.spark_secondary_color));
+ animationSpeed = a.getFloat(R.styleable.SparkButton_animationSpeed, 1);
+ // recycle typedArray
+ a.recycle();
+ }
+}
diff --git a/sparkbutton/src/main/java/com/varunest/sparkbutton/SparkButtonBuilder.java b/sparkbutton/src/main/java/com/varunest/sparkbutton/SparkButtonBuilder.java
new file mode 100644
index 000000000..f79e492e8
--- /dev/null
+++ b/sparkbutton/src/main/java/com/varunest/sparkbutton/SparkButtonBuilder.java
@@ -0,0 +1,61 @@
+package com.varunest.sparkbutton;
+
+import android.content.Context;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.DrawableRes;
+
+import com.varunest.sparkbutton.helpers.Utils;
+
+/**
+ * @author varun on 07/07/16.
+ */
+public class SparkButtonBuilder {
+ private final SparkButton sparkButton;
+ private final Context context;
+
+ public SparkButtonBuilder(Context context) {
+ this.context = context;
+ sparkButton = new SparkButton(context);
+ }
+
+ public SparkButtonBuilder setActiveImage(@DrawableRes int resourceId) {
+ sparkButton.setActiveImage(resourceId);
+ return this;
+ }
+
+ public SparkButtonBuilder setInactiveImage(@DrawableRes int resourceId) {
+ sparkButton.setInactiveImage(resourceId);
+ return this;
+ }
+
+ public SparkButtonBuilder setPrimaryColor(@ColorInt int color) {
+ sparkButton.setPrimaryColor(color);
+ return this;
+ }
+
+ public SparkButtonBuilder setSecondaryColor(int color) {
+ sparkButton.setSecondaryColor(color);
+ return this;
+ }
+
+ public SparkButtonBuilder setImageSizePx(int px) {
+ sparkButton.setImageSize(px);
+ return this;
+ }
+
+ public SparkButtonBuilder setImageSizeDp(int dp) {
+ sparkButton.setImageSize(Utils.dpToPx(context, dp));
+ return this;
+ }
+
+ public SparkButtonBuilder setAnimationSpeed(float speed) {
+ sparkButton.setAnimationSpeed(speed);
+ return this;
+ }
+
+ public SparkButton build() {
+ sparkButton.init();
+ return sparkButton;
+ }
+}
diff --git a/sparkbutton/src/main/java/com/varunest/sparkbutton/SparkEventListener.java b/sparkbutton/src/main/java/com/varunest/sparkbutton/SparkEventListener.java
new file mode 100644
index 000000000..2e02b0891
--- /dev/null
+++ b/sparkbutton/src/main/java/com/varunest/sparkbutton/SparkEventListener.java
@@ -0,0 +1,10 @@
+package com.varunest.sparkbutton;
+
+import androidx.annotation.NonNull;
+
+/**
+ * @author varun on 07/07/16.
+ */
+public interface SparkEventListener {
+ boolean onEvent(@NonNull SparkButton button, boolean buttonState);
+} \ No newline at end of file
diff --git a/sparkbutton/src/main/java/com/varunest/sparkbutton/helpers/SparkAnimationView.java b/sparkbutton/src/main/java/com/varunest/sparkbutton/helpers/SparkAnimationView.java
new file mode 100644
index 000000000..c7c5fb59f
--- /dev/null
+++ b/sparkbutton/src/main/java/com/varunest/sparkbutton/helpers/SparkAnimationView.java
@@ -0,0 +1,249 @@
+package com.varunest.sparkbutton.helpers;
+
+import android.animation.ArgbEvaluator;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.util.Property;
+import android.view.View;
+
+
+public class SparkAnimationView extends View {
+ public static final Property<SparkAnimationView, Float> INNER_CIRCLE_RADIUS_PROGRESS =
+ new Property<SparkAnimationView, Float>(Float.class, "innerCircleRadiusProgress") {
+ @Override
+ public Float get(SparkAnimationView object) {
+ return object.getInnerCircleRadiusProgress();
+ }
+
+ @Override
+ public void set(SparkAnimationView object, Float value) {
+ object.setInnerCircleRadiusProgress(value);
+ }
+ };
+ private static final int DOTS_COUNT = 12;
+ private static final int OUTER_DOTS_POSITION_ANGLE = 360 / DOTS_COUNT;
+ private static final ArgbEvaluator argbEvaluator = new ArgbEvaluator();
+ public static final Property<SparkAnimationView, Float> DOTS_PROGRESS = new Property<SparkAnimationView, Float>(Float.class, "dotsProgress") {
+ @Override
+ public Float get(SparkAnimationView object) {
+ return object.getCurrentProgress();
+ }
+
+ @Override
+ public void set(SparkAnimationView object, Float value) {
+ object.setCurrentProgress(value);
+ }
+ };
+ public static final Property<SparkAnimationView, Float> OUTER_CIRCLE_RADIUS_PROGRESS =
+ new Property<SparkAnimationView, Float>(Float.class, "outerCircleRadiusProgress") {
+ @Override
+ public Float get(SparkAnimationView object) {
+ return object.getOuterCircleRadiusProgress();
+ }
+
+ @Override
+ public void set(SparkAnimationView object, Float value) {
+ object.setOuterCircleRadiusProgress(value);
+ }
+ };
+ private final Paint[] dotsPaints = new Paint[4];
+ private final Paint circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private final Paint maskPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private int primaryColor = 0xFFFFC107;
+ private int primaryColorDark = 0xFFFF9800;
+ private int secondaryColor = 0xFFFF5722;
+ private int secondaryColorDark = 0xFFF44336;
+ private int centerX;
+ private int centerY;
+ private float maxOuterDotsRadius;
+ private float maxInnerDotsRadius;
+ private float maxDotSize;
+ private float currentProgress = 0;
+ private float currentRadius1 = 0;
+ private float currentDotSize1 = 0;
+ private float currentDotSize2 = 0;
+ private float currentRadius2 = 0;
+ private float outerCircleRadiusProgress = 0f;
+ private float innerCircleRadiusProgress = 0f;
+ private float maxCircleSize;
+
+ public SparkAnimationView(Context context) {
+ super(context);
+ init();
+ }
+
+ public SparkAnimationView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public SparkAnimationView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public SparkAnimationView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ init();
+ }
+
+ private void init() {
+ setLayerType(View.LAYER_TYPE_HARDWARE, null);
+
+ maxDotSize = Utils.dpToPx(getContext(), 4);
+ for (int i = 0; i < dotsPaints.length; i++) {
+ dotsPaints[i] = new Paint(Paint.ANTI_ALIAS_FLAG);
+ }
+
+ maskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ centerX = w / 2;
+ centerY = h / 2;
+ maxOuterDotsRadius = w / 2 - maxDotSize * 2;
+ maxInnerDotsRadius = 0.8f * maxOuterDotsRadius;
+ maxCircleSize = w / 4.3f;
+
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ drawOuterDotsFrame(canvas);
+ drawInnerDotsFrame(canvas);
+
+ canvas.drawCircle(getWidth() / 2, getHeight() / 2, outerCircleRadiusProgress * maxCircleSize, circlePaint);
+ canvas.drawCircle(getWidth() / 2, getHeight() / 2, innerCircleRadiusProgress * (maxCircleSize + 1), maskPaint);
+ }
+
+ public void setMaxDotSize(int pxUnits) {
+ maxDotSize = pxUnits;
+ }
+
+ private void drawOuterDotsFrame(Canvas canvas) {
+ for (int i = 0; i < DOTS_COUNT; i++) {
+ int cX = (int) (centerX + currentRadius1 * Math.cos(i * OUTER_DOTS_POSITION_ANGLE * Math.PI / 180));
+ int cY = (int) (centerY + currentRadius1 * Math.sin(i * OUTER_DOTS_POSITION_ANGLE * Math.PI / 180));
+ canvas.drawCircle(cX, cY, currentDotSize1, dotsPaints[i % dotsPaints.length]);
+ }
+ }
+
+ private void drawInnerDotsFrame(Canvas canvas) {
+ for (int i = 0; i < DOTS_COUNT; i++) {
+ int cX = (int) (centerX + currentRadius2 * Math.cos((i * OUTER_DOTS_POSITION_ANGLE - 10) * Math.PI / 180));
+ int cY = (int) (centerY + currentRadius2 * Math.sin((i * OUTER_DOTS_POSITION_ANGLE - 10) * Math.PI / 180));
+ canvas.drawCircle(cX, cY, currentDotSize2, dotsPaints[(i + 1) % dotsPaints.length]);
+ }
+ }
+
+ public float getCurrentProgress() {
+ return currentProgress;
+ }
+
+ public void setCurrentProgress(float currentProgress) {
+ this.currentProgress = currentProgress;
+
+ updateInnerDotsPosition();
+ updateOuterDotsPosition();
+ updateDotsPaints();
+ updateDotsAlpha();
+
+ postInvalidate();
+ }
+
+ private void updateInnerDotsPosition() {
+ if (currentProgress < 0.3f) {
+ this.currentRadius2 = (float) Utils.mapValueFromRangeToRange(currentProgress, 0, 0.3f, 0.f, maxInnerDotsRadius);
+ } else {
+ this.currentRadius2 = maxInnerDotsRadius;
+ }
+
+ if (currentProgress < 0.2) {
+ this.currentDotSize2 = maxDotSize;
+ } else if (currentProgress < 0.5) {
+ this.currentDotSize2 = (float) Utils.mapValueFromRangeToRange(currentProgress, 0.2f, 0.5f, maxDotSize, 0.3 * maxDotSize);
+ } else {
+ this.currentDotSize2 = (float) Utils.mapValueFromRangeToRange(currentProgress, 0.5f, 1f, maxDotSize * 0.3f, 0);
+ }
+
+ }
+
+ private void updateOuterDotsPosition() {
+ if (currentProgress < 0.3f) {
+ this.currentRadius1 = (float) Utils.mapValueFromRangeToRange(currentProgress, 0.0f, 0.3f, 0, maxOuterDotsRadius * 0.8f);
+ } else {
+ this.currentRadius1 = (float) Utils.mapValueFromRangeToRange(currentProgress, 0.3f, 1f, 0.8f * maxOuterDotsRadius, maxOuterDotsRadius);
+ }
+
+ if (currentProgress < 0.7) {
+ this.currentDotSize1 = maxDotSize;
+ } else {
+ this.currentDotSize1 = (float) Utils.mapValueFromRangeToRange(currentProgress, 0.7f, 1f, maxDotSize, 0);
+ }
+ }
+
+ private void updateDotsPaints() {
+ if (currentProgress < 0.5f) {
+ float progress = (float) Utils.mapValueFromRangeToRange(currentProgress, 0f, 0.5f, 0, 1f);
+ dotsPaints[0].setColor((Integer) argbEvaluator.evaluate(progress, primaryColor, primaryColorDark));
+ dotsPaints[1].setColor((Integer) argbEvaluator.evaluate(progress, primaryColorDark, secondaryColor));
+ dotsPaints[2].setColor((Integer) argbEvaluator.evaluate(progress, secondaryColor, secondaryColorDark));
+ dotsPaints[3].setColor((Integer) argbEvaluator.evaluate(progress, secondaryColorDark, primaryColor));
+ } else {
+ float progress = (float) Utils.mapValueFromRangeToRange(currentProgress, 0.5f, 1f, 0, 1f);
+ dotsPaints[0].setColor((Integer) argbEvaluator.evaluate(progress, primaryColorDark, secondaryColor));
+ dotsPaints[1].setColor((Integer) argbEvaluator.evaluate(progress, secondaryColor, secondaryColorDark));
+ dotsPaints[2].setColor((Integer) argbEvaluator.evaluate(progress, secondaryColorDark, primaryColor));
+ dotsPaints[3].setColor((Integer) argbEvaluator.evaluate(progress, primaryColor, primaryColorDark));
+ }
+ }
+
+ private void updateDotsAlpha() {
+ float progress = (float) Utils.clamp(currentProgress, 0.6f, 1f);
+ int alpha = (int) Utils.mapValueFromRangeToRange(progress, 0.6f, 1f, 255, 0);
+ dotsPaints[0].setAlpha(alpha);
+ dotsPaints[1].setAlpha(alpha);
+ dotsPaints[2].setAlpha(alpha);
+ dotsPaints[3].setAlpha(alpha);
+ }
+
+ public void setColors(int primaryColor, int secondaryColor) {
+ this.primaryColor = primaryColor;
+ this.primaryColorDark = Utils.darkenColor(primaryColor, 1.1f);
+ this.secondaryColor = secondaryColor;
+ this.secondaryColorDark = Utils.darkenColor(secondaryColor, 1.1f);
+ }
+
+ public float getInnerCircleRadiusProgress() {
+ return innerCircleRadiusProgress;
+ }
+
+ public void setInnerCircleRadiusProgress(float innerCircleRadiusProgress) {
+ this.innerCircleRadiusProgress = innerCircleRadiusProgress;
+ postInvalidate();
+ }
+
+ private void updateCircleColor() {
+ float colorProgress = (float) Utils.clamp(outerCircleRadiusProgress, 0.5, 1);
+ colorProgress = (float) Utils.mapValueFromRangeToRange(colorProgress, 0.5f, 1f, 0f, 1f);
+ this.circlePaint.setColor((Integer) argbEvaluator.evaluate(colorProgress, primaryColor, secondaryColor));
+ }
+
+ public float getOuterCircleRadiusProgress() {
+ return outerCircleRadiusProgress;
+ }
+
+ public void setOuterCircleRadiusProgress(float outerCircleRadiusProgress) {
+ this.outerCircleRadiusProgress = outerCircleRadiusProgress;
+ updateCircleColor();
+ postInvalidate();
+ }
+} \ No newline at end of file
diff --git a/sparkbutton/src/main/java/com/varunest/sparkbutton/helpers/Utils.java b/sparkbutton/src/main/java/com/varunest/sparkbutton/helpers/Utils.java
new file mode 100644
index 000000000..6a42dc42e
--- /dev/null
+++ b/sparkbutton/src/main/java/com/varunest/sparkbutton/helpers/Utils.java
@@ -0,0 +1,30 @@
+package com.varunest.sparkbutton.helpers;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.util.DisplayMetrics;
+
+import androidx.annotation.NonNull;
+
+public class Utils {
+ public static double mapValueFromRangeToRange(double value, double fromLow, double fromHigh, double toLow, double toHigh) {
+ return toLow + ((value - fromLow) / (fromHigh - fromLow) * (toHigh - toLow));
+ }
+
+ public static double clamp(double value, double low, double high) {
+ return Math.min(Math.max(value, low), high);
+ }
+
+ public static int darkenColor(int color, float multiplier) {
+ float[] hsv = new float[3];
+
+ Color.colorToHSV(color, hsv);
+ hsv[2