Fix issue #745 - Update library for bottom buttons.

This commit is contained in:
Thomas 2023-01-13 16:19:50 +01:00
parent d70c285bea
commit 107ac13e15
21 changed files with 865 additions and 35 deletions

View File

@ -78,7 +78,7 @@ allprojects {
}
dependencies {
implementation project(':autoimageslider')
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'androidx.appcompat:appcompat:1.6.0'
implementation 'com.google.android.material:material:1.7.0'
@ -94,7 +94,6 @@ dependencies {
implementation "org.conscrypt:conscrypt-android:2.5.2"
implementation 'com.vanniktech:emoji-one:0.6.0'
implementation 'com.github.GrenderG:Toasty:1.5.2'
implementation 'org.framagit.tom79:SparkButton:1.0.13'
implementation "com.github.bumptech.glide:glide:4.14.2"
implementation "com.github.bumptech.glide:okhttp3-integration:4.14.2"
implementation("com.github.bumptech.glide:recyclerview-integration:4.14.2") {
@ -107,7 +106,7 @@ dependencies {
implementation 'com.github.mergehez:ArgPlayer:v3.1'
implementation project(path: ':mytransl')
implementation project(path: ':ratethisapp')
implementation project(path: ':sparkbutton')
implementation 'com.burhanrashid52:photoeditor:1.5.1'
implementation("com.vanniktech:android-image-cropper:4.3.3")

View File

@ -638,21 +638,13 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
}
holder.binding.actionButtonFavorite.pressOnTouch(false);
holder.binding.actionButtonBoost.pressOnTouch(false);
holder.binding.actionButtonBookmark.pressOnTouch(false);
holder.binding.actionButtonFavorite.setActiveImage(R.drawable.ic_round_star_24);
holder.binding.actionButtonFavorite.setInactiveImage(R.drawable.ic_round_star_border_24);
holder.binding.actionButtonBookmark.setActiveImage(R.drawable.ic_round_bookmark_24);
holder.binding.actionButtonBookmark.setInactiveImage(R.drawable.ic_round_bookmark_border_24);
holder.binding.actionButtonBoost.setActiveImage(R.drawable.ic_round_repeat_24);
holder.binding.actionButtonBoost.setInactiveImage(R.drawable.ic_round_repeat_24);
holder.binding.actionButtonFavorite.setDisableCircle(true);
holder.binding.actionButtonBoost.setDisableCircle(true);
holder.binding.actionButtonBookmark.setDisableCircle(true);
holder.binding.actionButtonFavorite.setActiveImageTint(R.color.marked_icon);
holder.binding.actionButtonBoost.setActiveImageTint(R.color.boost_icon);
holder.binding.actionButtonBookmark.setActiveImageTint(R.color.marked_icon);
applyColor(context, holder);
if (status.pinned) {
@ -2319,9 +2311,6 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
Helper.changeDrawableColor(context, R.drawable.ic_bot, theme_icons_color);
Helper.changeDrawableColor(context, R.drawable.ic_round_reply_24, theme_icons_color);
Helper.changeDrawableColor(context, holder.binding.actionButtonTranslate, theme_icons_color);
holder.binding.actionButtonFavorite.setInActiveImageTintColor(theme_icons_color);
holder.binding.actionButtonBookmark.setInActiveImageTintColor(theme_icons_color);
holder.binding.actionButtonBoost.setInActiveImageTintColor(theme_icons_color);
holder.binding.replyCount.setTextColor(theme_icons_color);
}
if (theme_statuses_color != -1) {

View File

@ -1,7 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:tint="@color/marked_icon"
android:viewportWidth="24"
android:viewportHeight="24">
<path

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="@color/boost_icon"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M7,7h10v1.79c0,0.45 0.54,0.67 0.85,0.35l2.79,-2.79c0.2,-0.2 0.2,-0.51 0,-0.71l-2.79,-2.79c-0.31,-0.31 -0.85,-0.09 -0.85,0.36L17,5L6,5c-0.55,0 -1,0.45 -1,1v4c0,0.55 0.45,1 1,1s1,-0.45 1,-1L7,7zM17,17L7,17v-1.79c0,-0.45 -0.54,-0.67 -0.85,-0.35l-2.79,2.79c-0.2,0.2 -0.2,0.51 0,0.71l2.79,2.79c0.31,0.31 0.85,0.09 0.85,-0.36L7,19h11c0.55,0 1,-0.45 1,-1v-4c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v3z" />
</vector>

View File

@ -1,7 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:tint="@color/marked_icon"
android:viewportWidth="24"
android:viewportHeight="24">
<path

View File

@ -17,6 +17,7 @@
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:sparkbutton="http://schemas.android.com/apk/res-auto"
android:id="@+id/cardview_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -655,12 +656,11 @@
android:layout_height="48dp"
android:adjustViewBounds="true"
android:contentDescription="@string/reblog_add"
app:sparkbutton_activeImage="@drawable/ic_round_repeat_24"
app:sparkbutton_animationSpeed="1.5"
app:sparkbutton_iconSize="24dp"
app:sparkbutton_inActiveImage="@drawable/ic_round_repeat_24"
app:sparkbutton_primaryColor="@color/boost_icon"
app:sparkbutton_secondaryColor="@color/boost_icon" />
app:activeImage="@drawable/ic_round_repeat_active_24"
app:iconSize="28dp"
app:inactiveImage="@drawable/ic_round_repeat_24"
app:primaryColor="@color/boost_icon"
app:secondaryColor="@color/boost_icon" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/action_button_quote"
@ -691,12 +691,12 @@
android:layout_gravity="center"
android:adjustViewBounds="true"
android:contentDescription="@string/favourite_add"
app:sparkbutton_activeImage="@drawable/ic_round_star_24"
app:sparkbutton_animationSpeed="1.5"
app:sparkbutton_iconSize="24dp"
app:sparkbutton_inActiveImage="@drawable/ic_round_star_border_24"
app:sparkbutton_primaryColor="@color/marked_icon"
app:sparkbutton_secondaryColor="@color/marked_icon" />
app:activeImage="@drawable/ic_round_star_24"
app:animationSpeed="1.5"
app:inactiveImage="@drawable/ic_round_star_border_24"
app:primaryColor="@color/marked_icon"
app:secondaryColor="@color/marked_icon"
sparkbutton:iconSize="28dp" />
<com.varunest.sparkbutton.SparkButton
android:id="@+id/action_button_bookmark"
@ -709,12 +709,12 @@
android:layout_gravity="center"
android:adjustViewBounds="true"
android:contentDescription="@string/bookmark_add"
app:sparkbutton_activeImage="@drawable/ic_round_bookmark_24"
app:sparkbutton_animationSpeed="1.5"
app:sparkbutton_iconSize="24dp"
app:sparkbutton_inActiveImage="@drawable/ic_round_bookmark_border_24"
app:sparkbutton_primaryColor="@color/marked_icon"
app:sparkbutton_secondaryColor="@color/marked_icon" />
app:activeImage="@drawable/ic_round_bookmark_24"
app:animationSpeed="1.5"
app:inactiveImage="@drawable/ic_round_bookmark_border_24"
app:primaryColor="@color/marked_icon"
app:secondaryColor="@color/marked_icon"
sparkbutton:iconSize="28dp" />
<androidx.appcompat.widget.AppCompatImageButton

View File

@ -3,3 +3,4 @@ include ':app'
include ':autoimageslider'
include ':mytransl'
include ':ratethisapp'
include ':sparkbutton'

1
sparkbutton/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

27
sparkbutton/build.gradle Normal file
View File

@ -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'
}

View File

@ -0,0 +1,3 @@
POM_NAME=SparkButton
POM_ARTIFACT_ID=sparkbutton
POM_PACKAGING=aar

View File

@ -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
}
}

17
sparkbutton/proguard-rules.pro vendored Normal file
View File

@ -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 *;
#}

View File

@ -0,0 +1,4 @@
<manifest package="com.varunest.sparkbutton">
<application />
</manifest>

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -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] *= multiplier; // value component
return Color.HSVToColor(hsv);
}
public static int dpToPx(@NonNull Context context, int dp) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
return Math.round(dp * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT));
}
}

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="SparkButton">
<attr name="iconSize" format="dimension|reference" />
<attr name="activeImage" format="reference" />
<attr name="inactiveImage" format="reference" />
<attr name="primaryColor" format="reference" />
<attr name="secondaryColor" format="reference" />
<attr name="animationSpeed" format="float" />
</declare-styleable>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="spark_primary_color">#FFFFC107</color>
<color name="spark_secondary_color">#FFFF5722</color>
<color name="spark_image_tint">#00000000</color>
</resources>

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">SparkButton</string>
</resources>