Create Stunning Firework Like Effects for Android Likes with Canvas and Animators
This tutorial explains how to implement a firework‑style like animation in Android by defining particle objects, drawing them on a Canvas, controlling their motion with ValueAnimator and various interpolators, and assembling the animation sequence to produce a festive, high‑frame‑rate visual effect.
Introduction
Using a firework‑style like effect is a common way to boost user interaction in apps. This guide shows how to build such an effect with holiday icons, creating a festive atmosphere and increasing user engagement.
Effect Screenshot
Implementation Overview
The firework effect consists of two main parts: particles emitted from a launch point and spark particles generated after the explosion. Each particle holds a bitmap and is drawn on a Canvas, forming the overall firework pattern.
1. Define Particle Object
public class Particle {
Matrix mMatrix;
Paint mPaint;
Bitmap mImage;
float mInitialX, mInitialY; // initial position
float mSpeedX, mSpeedY; // speed (px/ms)
float mRotation; // rotation angle
float mScale = 1f; // scale
int mAlpha = 255; // opacity
float mCurrentX, mCurrentY; // current position
protected Particle(Bitmap bitmap) {
mMatrix = new Matrix();
mPaint = new Paint();
mImage = bitmap;
}
public boolean update(long miliseconds) {
updateLocation(this, miliseconds);
updateScale(this, miliseconds);
updateAlpha(this, miliseconds);
return true;
}
public void draw(Canvas c) {
mMatrix.reset();
mMatrix.postRotate(mRotation, mBitmapHalfWidth, mBitmapHalfHeight);
mMatrix.postScale(mScale, mScale, mBitmapHalfWidth, mBitmapHalfHeight);
mMatrix.postTranslate(mCurrentX, mCurrentY);
mPaint.setAlpha(mAlpha);
c.drawBitmap(mImage, mMatrix, mPaint);
}
}2. Draw on Canvas
public class ParticleLikeFieldView extends View {
List<Particle> fireworkList; // firework particles
List<List<Particle>> sparkOuterList; // outer spark group
List<List<Particle>> sparkInnerList; // inner spark group
boolean isFireworkDead = false; // explosion state
@Override
protected void onDraw(Canvas canvas) {
synchronized (this) {
if (!isFireworkDead) {
for (Particle p : fireworkList) {
p.draw(canvas);
}
} else {
for (List<Particle> outer : sparkOuterList) {
for (Particle p : outer) {
p.draw(canvas);
}
}
for (List<Particle> inner : sparkInnerList) {
for (Particle p : inner) {
p.draw(canvas);
}
}
}
}
}
}3. Add and Remove the Drawing View
private void addDrawingView() {
ParticleLikeFieldView mDrawingView = new ParticleLikeFieldView(context);
ViewGroup mParentView = (ViewGroup) activity.findViewById(android.R.id.content);
mParentView.addView(mDrawingView);
}
private void cleanupDrawingView() {
mParentView.removeView(mDrawingView);
mParentView.postInvalidate();
mDrawingView = null;
}4. Create Animations
The animation sequence is split into several ValueAnimators:
fireworkAnim – launches the firework (800 ms)
sparkOuterAnim – outer spark expansion (300 ms)
sparkInnerAnim – inner spark expansion (300 ms)
nullOuterAnim – pause before inner sparks (100 ms)
fireworkAnim Example
ValueAnimator fireworkAnim = ValueAnimator.ofInt(0, fireworkDuration);
fireworkAnim.setDuration(fireworkDuration);
fireworkAnim.setInterpolator(new DecelerateInterpolator());
fireworkAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int miliseconds = (Integer) animation.getAnimatedValue();
particle.update(miliseconds);
mDrawingView.postInvalidate();
}
});
fireworkAnim.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationEnd(Animator animation) {
mDrawingView.isFireworkDead = true;
}
@Override
public void onAnimationCancel(Animator animation) {
cleanupDrawingView();
}
// other callbacks omitted for brevity
});Combine Animations
AnimatorSet set = new AnimatorSet();
set.play(fireworkAnim).before(sparkOuterAnim);
set.play(sparkOuterAnim).with(nullOuterAnim);
set.play(nullOuterAnim).before(sparkInnerAnim);
set.start();5. Interpolators
Interpolators map the animation progress (0‑1) to a fraction that determines the speed curve.
TimeInterpolator Interface
public interface TimeInterpolator {
float getInterpolation(float input);
}LinearInterpolator (constant speed)
public float getInterpolation(float input) {
return input;
}AccelerateDecelerateInterpolator (ease‑in‑ease‑out)
public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f);
}CycleInterpolator (repeating)
public float getInterpolation(float input) {
return (float)(Math.sin(2 * mCycles * Math.PI * input));
}Particle Property Calculations
1. Determine Launch Position
int[] location = new int[2];
emitter.getLocationInWindow(location);
int emitterX = location[0] + emitter.getWidth() / 2;
int emitterY = location[1] + emitter.getHeight() / 2;
particle.mInitialX = emitterX - mBitmapHalfWidth;
particle.mInitialY = emitterY - mBitmapHalfHeight;
particle.mCurrentX = particle.mInitialX;
particle.mCurrentY = particle.mInitialY;2. Initial Speed and Angle
int angle = random.nextInt(maxAngle - minAngle) + minAngle;
float angleOuter = 45 * (n - 1); // outer spark
float angleInner = 45 * (n - 1); // inner spark
double angleInRads = Math.toRadians(angle);
particle.mSpeedX = (float) (speed * Math.cos(angleInRads));
particle.mSpeedY = (float) (speed * Math.sin(angleInRads));
particle.mRotation = angle + 90;3. Scale
float mInitialValue = 1.0f;
float mFinalValue = 2.0f;
Interpolator mInterpolator = new LinearInterpolator();
public boolean updateScale(Particle particle, long ms, long start, long end) {
if (ms < start) {
particle.mScale = mInitialValue;
} else if (ms > end) {
particle.mScale = mFinalValue;
} else {
float input = (ms - start) * 1f / (end - start);
float fraction = mInterpolator.getInterpolation(input);
particle.mScale = mInitialValue + (mFinalValue - mInitialValue) * fraction;
}
return true;
}4. Opacity
int mInitialValue = 255;
int mFinalValue = 0;
Interpolator mInterpolator = new AccelerateInterpolator();
public boolean updateAlpha(Particle particle, long ms, long start, long end) {
if (ms < start) {
particle.mAlpha = mInitialValue;
} else if (ms > end) {
particle.mAlpha = mFinalValue;
} else {
float input = (ms - start) * 1f / (end - start);
float fraction = mInterpolator.getInterpolation(input);
particle.mAlpha = (int) (mInitialValue + (mFinalValue - mInitialValue) * fraction);
}
return true;
}5. Position Update
public boolean updateLocation(Particle particle, long ms) {
particle.mCurrentX = mInitialX + mSpeedX * ms;
particle.mCurrentY = mInitialY + mSpeedY * ms;
return true;
}Conclusion
Using Canvas to render high‑frame‑rate particle effects provides a flexible way to create custom animations on Android. Property animators and the rich set of interpolators (including custom ones) enable fine‑grained control over speed curves, making it easy to build fireworks, bubbles, lens‑flaring, and many other visual effects.
Sohu Tech Products
A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
