diff options
author | Thomas <tschneider.ac@gmail.com> | 2023-01-13 16:19:50 +0100 |
---|---|---|
committer | Thomas <tschneider.ac@gmail.com> | 2023-01-13 16:19:50 +0100 |
commit | 107ac13e1547c50424497f29116d1aaf9bc6ab69 (patch) | |
tree | e7f37e97b66858ef8e42183937fc22ec16daa027 /sparkbutton | |
parent | d70c285beaf058f8817dffc04b980f9c3cb93e31 (diff) |
Fix issue #745 - Update library for bottom buttons.
Diffstat (limited to 'sparkbutton')
-rw-r--r-- | sparkbutton/.gitignore | 1 | ||||
-rw-r--r-- | sparkbutton/build.gradle | 27 | ||||
-rw-r--r-- | sparkbutton/gradle.properties | 3 | ||||
-rw-r--r-- | sparkbutton/maven-push.gradle | 96 | ||||
-rw-r--r-- | sparkbutton/proguard-rules.pro | 17 | ||||
-rw-r--r-- | sparkbutton/src/main/AndroidManifest.xml | 4 | ||||
-rw-r--r-- | sparkbutton/src/main/java/com/varunest/sparkbutton/SparkButton.java | 313 | ||||
-rw-r--r-- | sparkbutton/src/main/java/com/varunest/sparkbutton/SparkButtonBuilder.java | 61 | ||||
-rw-r--r-- | sparkbutton/src/main/java/com/varunest/sparkbutton/SparkEventListener.java | 10 | ||||
-rw-r--r-- | sparkbutton/src/main/java/com/varunest/sparkbutton/helpers/SparkAnimationView.java | 249 | ||||
-rw-r--r-- | sparkbutton/src/main/java/com/varunest/sparkbutton/helpers/Utils.java | 30 | ||||
-rw-r--r-- | sparkbutton/src/main/res/values/attrs.xml | 11 | ||||
-rw-r--r-- | sparkbutton/src/main/res/values/colors.xml | 6 | ||||
-rw-r--r-- | sparkbutton/src/main/res/values/strings.xml | 3 |
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; + |