Last active
March 8, 2016 21:11
-
-
Save fnk0/53597ac503631a6e3c0d to your computer and use it in GitHub Desktop.
Bang Animation View similar to Twitter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public interface BangAnimationListener { | |
/** | |
* <p>Notifies the start of the animation.</p> | |
*/ | |
void onAnimationStart(); | |
/** | |
* <p>Notifies the end of the animation. </p> | |
*/ | |
void onAnimationEnd(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.gabilheri.pawsalert.ui.animations; | |
import android.animation.Animator; | |
import android.animation.AnimatorListenerAdapter; | |
import android.animation.ValueAnimator; | |
import android.annotation.TargetApi; | |
import android.app.Activity; | |
import android.content.Context; | |
import android.graphics.Canvas; | |
import android.graphics.Color; | |
import android.graphics.Paint; | |
import android.graphics.Rect; | |
import android.os.Build; | |
import android.util.AttributeSet; | |
import android.view.View; | |
import android.view.ViewGroup; | |
import android.view.Window; | |
import android.view.animation.OvershootInterpolator; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.List; | |
import java.util.Random; | |
public class BangAnimationView extends View { | |
int[] colors = {0xFFDF4288, 0xFFCD8BF8, 0XFF2B9DF2, 0XFFA4EEB4, 0XFFE097CA, 0XFFCAACC6, 0XFFC5A5FC, 0XFFF5BC16, 0XFFF2DFC8, 0XFFE1BE8E, 0XFFC8C79D}; | |
List<Dot> dotList = new ArrayList<>(); | |
private long ANIMATE_DURATION = 1000; | |
private float MAX_RADIUS = 150; | |
private float MAX_CIRCLE_RADIUS = 100; | |
private float progress; | |
private Paint circlePaint; | |
private float RING_WIDTH = 10; | |
private float P1 = 0.15f; | |
private float P2 = 0.28f; | |
private float P3 = 0.30f; | |
private int DOT_NUMBER = 16; | |
private float DOT_BIG_RADIUS = 8; | |
private float DOT_SMALL_RADIUS = 5; | |
private int[] mExpandInset = new int[2]; | |
private BangAnimationListener mListener; | |
private int centerY; | |
private int centerX; | |
public BangAnimationView(Context context) { | |
super(context); | |
init(null, 0); | |
} | |
public BangAnimationView(Context context, AttributeSet attrs) { | |
super(context, attrs); | |
init(attrs, 0); | |
} | |
public BangAnimationView(Context context, AttributeSet attrs, int defStyleAttr) { | |
super(context, attrs, defStyleAttr); | |
init(attrs, defStyleAttr); | |
} | |
@TargetApi(Build.VERSION_CODES.LOLLIPOP) | |
public BangAnimationView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { | |
super(context, attrs, defStyleAttr, defStyleRes); | |
init(attrs, defStyleAttr); | |
} | |
public static BangAnimationView attach2Window(Activity activity) { | |
ViewGroup rootView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT); | |
BangAnimationView smallBang = new BangAnimationView(activity); | |
rootView.addView(smallBang, new ViewGroup.LayoutParams( | |
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); | |
return smallBang; | |
} | |
private void init(AttributeSet attrs, int defStyleAttr) { | |
circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG); | |
circlePaint.setStyle(Paint.Style.FILL); | |
circlePaint.setColor(Color.BLACK); | |
} | |
/** | |
* listener for animation change time | |
* | |
* @param listener | |
*/ | |
public void setListener(BangAnimationListener listener) { | |
this.mListener = listener; | |
} | |
/** | |
* set different colors for last dots | |
* | |
* @param newColors | |
*/ | |
public void setColors(int[] newColors) { | |
this.colors = Arrays.copyOf(newColors, newColors.length); | |
} | |
/** | |
* set small dot number | |
* @param dotNumber | |
*/ | |
public void setDotNumber(int dotNumber){ | |
DOT_NUMBER = dotNumber; | |
} | |
public void bang(final View view, BangAnimationListener listener) { | |
bang(view, -1, listener); | |
} | |
public void bang(final View view, float radius, BangAnimationListener listener) { | |
// set listener | |
if (listener != null) { | |
setListener(listener); | |
this.mListener.onAnimationStart(); | |
} | |
Rect r = new Rect(); | |
view.getGlobalVisibleRect(r); | |
int[] location = new int[2]; | |
getLocationOnScreen(location); | |
r.offset(-location[0], -location[1]); | |
r.inset(-mExpandInset[0], -mExpandInset[1]); | |
centerX = r.left + r.width() / 2; | |
centerY = r.top + r.height() / 2; | |
if (radius != -1) { | |
initRadius(radius); | |
} else { | |
initRadius(Math.max(r.width(),r.height())); | |
} | |
view.setScaleX(0.1f); | |
view.setScaleY(0.1f); | |
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f).setDuration((long) (ANIMATE_DURATION * 0.5f)); | |
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { | |
@Override | |
public void onAnimationUpdate(ValueAnimator animation) { | |
float animatedFraction = animation.getAnimatedFraction(); | |
view.setScaleX(0.1f + animatedFraction * 0.9f); | |
view.setScaleY(0.1f + animatedFraction * 0.9f); | |
} | |
}); | |
animator.setInterpolator(new OvershootInterpolator(2)); | |
animator.setStartDelay((long) (ANIMATE_DURATION * P3)); | |
animator.start(); | |
bang(); | |
} | |
private void initRadius(float max_circle_radius) { | |
MAX_CIRCLE_RADIUS = max_circle_radius; | |
MAX_RADIUS = MAX_CIRCLE_RADIUS * 1.1f; | |
DOT_BIG_RADIUS = MAX_CIRCLE_RADIUS * 0.07f; | |
DOT_SMALL_RADIUS = DOT_BIG_RADIUS * 0.5f; | |
} | |
public void bang(final View view) { | |
bang(view, null); | |
} | |
private void bang() { | |
ValueAnimator valueAnimator = new ValueAnimator().ofFloat(0, 1).setDuration(ANIMATE_DURATION); | |
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { | |
@Override | |
public void onAnimationUpdate(ValueAnimator animation) { | |
progress = (float) animation.getAnimatedValue(); | |
invalidate(); | |
} | |
}); | |
valueAnimator.start(); | |
valueAnimator.addListener(new AnimatorListenerAdapter() { | |
@Override | |
public void onAnimationEnd(Animator animation) { | |
if (mListener != null) { | |
mListener.onAnimationEnd(); | |
} | |
} | |
}); | |
initDots(); | |
} | |
private void initDots() { | |
Random random = new Random(System.currentTimeMillis()); | |
for (int i = 0; i < DOT_NUMBER * 2; i++) { | |
Dot dot = new Dot(); | |
dot.startColor = colors[random.nextInt(99999) % colors.length]; | |
dot.endColor = colors[random.nextInt(99999) % colors.length]; | |
dotList.add(dot); | |
} | |
} | |
@Override | |
protected void onDraw(Canvas canvas) { | |
if (progress >= 0 && progress <= P1) { | |
float progress1 = 1f / P1 * progress; | |
if (progress1 > 1) progress1 = 1; | |
int startColor = colors[0]; | |
int endColor = colors[1]; | |
circlePaint.setStyle(Paint.Style.FILL); | |
circlePaint.setColor(evaluateColor(startColor, endColor, progress1)); | |
canvas.drawCircle(centerX, centerY, MAX_CIRCLE_RADIUS * progress1, circlePaint); | |
} else if (progress > P1) { | |
if (progress > P1 && progress <= P3) { | |
float progress2 = (progress - P1) / (P3 - P1); | |
if (progress2 < 0) progress2 = 0; | |
if (progress2 > 1) progress2 = 1; | |
circlePaint.setStyle(Paint.Style.STROKE); | |
float strokeWidth = (MAX_CIRCLE_RADIUS) * (1 - progress2); | |
circlePaint.setStrokeWidth(strokeWidth); | |
canvas.drawCircle(centerX, centerY, (MAX_CIRCLE_RADIUS) * progress2 + strokeWidth / 2, circlePaint); | |
} | |
if (progress >= P2) { | |
circlePaint.setStyle(Paint.Style.FILL); | |
float progress3 = (progress - P2) / (1 - P2); | |
float r = MAX_CIRCLE_RADIUS + progress3 * (MAX_RADIUS - MAX_CIRCLE_RADIUS); | |
for (int i = 0; i < dotList.size(); i += 2) { | |
Dot dot = dotList.get(i); | |
circlePaint.setColor(evaluateColor(dot.startColor, dot.endColor, progress3)); | |
float x = (float) (r * Math.cos(i * 2 * Math.PI / DOT_NUMBER)) + centerX; | |
float y = (float) (r * Math.sin(i * 2 * Math.PI / DOT_NUMBER)) + centerY; | |
canvas.drawCircle(x, y, DOT_BIG_RADIUS * (1 - progress3), circlePaint); | |
Dot dot2 = dotList.get(i + 1); | |
circlePaint.setColor(evaluateColor(dot2.startColor, dot2.endColor, progress3)); | |
float x2 = (float) (r * Math.cos(i * 2 * Math.PI / DOT_NUMBER + 0.2)) + centerX; | |
float y2 = (float) (r * Math.sin(i * 2 * Math.PI / DOT_NUMBER + 0.2)) + centerY; | |
canvas.drawCircle(x2, y2, DOT_SMALL_RADIUS * (1 - progress3), circlePaint); | |
} | |
} | |
} | |
} | |
private int evaluateColor(int startValue, int endValue, float fraction) { | |
if (fraction <= 0) { | |
return startValue; | |
} | |
if (fraction >= 1) { | |
return endValue; | |
} | |
int startInt = startValue; | |
int startA = (startInt >> 24) & 0xff; | |
int startR = (startInt >> 16) & 0xff; | |
int startG = (startInt >> 8) & 0xff; | |
int startB = startInt & 0xff; | |
int endInt = endValue; | |
int endA = (endInt >> 24) & 0xff; | |
int endR = (endInt >> 16) & 0xff; | |
int endG = (endInt >> 8) & 0xff; | |
int endB = endInt & 0xff; | |
return ((startA + (int) (fraction * (endA - startA))) << 24) | ((startR + (int) (fraction * (endR - startR))) << 16) | ((startG + (int) (fraction * (endG - startG))) << 8) | ((startB + (int) (fraction * (endB - startB)))); | |
} | |
class Dot { | |
int startColor; | |
int endColor; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment