mirror of https://codeberg.org/tom79/Fedilab
Fix issue #745 - Update library for bottom buttons.
parent
d70c285bea
commit
107ac13e15
@ -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>
|
@ -0,0 +1 @@
|
|||||||
|
/build
|
@ -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'
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
POM_NAME=SparkButton
|
||||||
|
POM_ARTIFACT_ID=sparkbutton
|
||||||
|
POM_PACKAGING=aar
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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 *;
|
||||||
|
#}
|
@ -0,0 +1,4 @@
|
|||||||
|
<manifest package="com.varunest.sparkbutton">
|
||||||
|
|
||||||
|
<application />
|
||||||
|
</manifest>
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
@ -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>
|
@ -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>
|
@ -0,0 +1,3 @@
|
|||||||
|
<resources>
|
||||||
|
<string name="app_name">SparkButton</string>
|
||||||
|
</resources>
|
Loading…
Reference in new issue