烟花效果
实现原理
- SurfaceView + HandlerThread
为什么使用SurfaceView?
因为SurfaceView在子线程刷新不会阻塞主线程,适用于界面频繁更新、对帧率要求较高的情况,SurfaceView可以控制刷新频率,比如10ms刷新一次,SurfaceView底层利用双缓存机制,绘图时不会出现闪烁问题。 - ValueAnimator控制位移、缩放和透明度
总的来说,非常简单就能实现,利用ValueAnimator属性动画控制两张图片的位移、缩放和透明度变化,将这些动画效果组合起来就行了,直接贴代码。
代码实现
public class FireworkShow extends SurfaceView implements SurfaceHolder.Callback {
private DrawTask mDrawTask; //绘制UI线程
private DrawView mLauncherView; //烟花爆炸的引信
private DrawView mFirework; //爆炸的烟花
private int mWidth; //控件宽度
private int mHeight; //控件高度
private Paint mPaint;
private int top; //引信上升的位移
private int lx, ly; //引信消失的位置
private ValueAnimator mLauncherRiseAnimator; //引信上升效果
private ValueAnimator mLauncherAlphaAnimator; //引信消失效果
private ValueAnimator mBoomAnimator; //烟花爆炸效果
private ValueAnimator mBoomEndAnimator; //烟花爆炸消失效果
private boolean isDisappear = true; //烟花是否消失
private static final int MSG_DRAW = 1;
//获取Looper
private static final HandlerThread mHandlerThread = new HandlerThread(FireworkShow.class.getName());
static {
mHandlerThread.start();
}
//Handler消息处理
private final Handler.Callback mCallback = new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message message) {
if (message.what == MSG_DRAW) {
mHandler.post(mDrawTask);
mHandler.sendEmptyMessageDelayed(MSG_DRAW, 10);
}
return true;
}
};
//用Looper创建弱引用Handler
private final WeakHandler mHandler = new WeakHandler(mCallback, mHandlerThread.getLooper());
private static class WeakHandler extends Handler {
private final WeakReference<Callback> mWeakReference;
public WeakHandler(Callback callback, Looper looper) {
super(looper);
mWeakReference = new WeakReference<>(callback);
}
@Override
public void handleMessage(@NonNull Message msg) {
Callback mCallback = mWeakReference.get();
if (mCallback != null) {
mCallback.handleMessage(msg);
}
}
}
public FireworkShow(Context context) {
this(context, null);
}
public FireworkShow(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
init();
}
private void init() {
SurfaceHolder holder = getHolder();
holder.addCallback(this);
holder.setKeepScreenOn(true);
holder.setFormat(PixelFormat.TRANSPARENT);
mPaint = new Paint();
}
@Override
public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
if (mDrawTask == null) {
mDrawTask = new DrawTask(surfaceHolder, this);
}
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int format, int width, int height) {
this.mWidth = width;
this.mHeight = height;
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
}
@Override
public void draw(Canvas canvas) {
if (canvas == null) return;
super.draw(canvas);
//清空界面
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
if (mLauncherView == null || mFirework == null) return;
mLauncherView.top = mHeight - mLauncherView.bitmap.getHeight() - top;
mLauncherView.left = (mWidth - mLauncherView.bitmap.getWidth()) / 2;
mPaint.setAlpha(mLauncherView.alpha);
canvas.drawBitmap(mLauncherView.bitmap, mLauncherView.left, mLauncherView.top, mPaint);
canvas.save();
mPaint.setAlpha(mFirework.alpha);
mFirework.left = (int) (lx - mFirework.bitmap.getWidth() * mFirework.scale / 2.0f);
mFirework.top = (int) (ly - mFirework.bitmap.getHeight() * mFirework.scale / 2.0f);
canvas.scale(mFirework.scale, mFirework.scale, mFirework.left, mFirework.top);
canvas.drawBitmap(mFirework.bitmap, mFirework.left, mFirework.top, mPaint);
canvas.restore();
}
public void startFireworkShow() {
mHandler.sendEmptyMessage(MSG_DRAW);
initDrawView();
//开始动画效果
mLauncherRiseAnimator.start();
}
public void stopFireworkShow() {
mHandler.removeMessages(MSG_DRAW);
mLauncherRiseAnimator.cancel();
mLauncherAlphaAnimator.cancel();
mBoomAnimator.cancel();
mBoomEndAnimator.cancel();
}
private void initDrawView() {
mLauncherView = new DrawView();
mLauncherView.bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.launcher);
mFirework = new DrawView();
mFirework.bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.firework);
mLauncherRiseAnimator = ValueAnimator.ofInt(0, 1400);
mLauncherRiseAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
mLauncherRiseAnimator.setDuration(4000);
mLauncherRiseAnimator.addUpdateListener(valueAnimator -> top = (int) valueAnimator.getAnimatedValue());
mLauncherRiseAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
isDisappear = false;
//烟花不显示
mFirework.alpha = 0;
//引信发射
mLauncherView.alpha = 255;
mLauncherAlphaAnimator.start();
}
@Override
public void onAnimationEnd(Animator animator) {
//记录引信消失的位置
lx = mLauncherView.left;
ly = mLauncherView.top;
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
mLauncherAlphaAnimator = ValueAnimator.ofInt(255, 50);
mLauncherAlphaAnimator.setInterpolator(new LinearInterpolator());
mLauncherAlphaAnimator.setDuration(4000);
mLauncherAlphaAnimator.addUpdateListener(valueAnimator -> mLauncherView.alpha = (int) valueAnimator.getAnimatedValue());
mLauncherAlphaAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
mLauncherView.alpha = 0; //引信消失
//烟花显示
mBoomAnimator.start();
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
mBoomAnimator = ValueAnimator.ofFloat(0.0f, 2.0f);
mBoomAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
mBoomAnimator.setDuration(2000);
mBoomAnimator.setStartDelay(1000);
mBoomAnimator.addUpdateListener(valueAnimator -> mFirework.scale = (float) valueAnimator.getAnimatedValue());
mBoomAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
//烟花消失效果
mBoomEndAnimator.start();
}
@Override
public void onAnimationEnd(Animator animator) {
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
mBoomEndAnimator = ValueAnimator.ofInt(255, 0);
mBoomAnimator.setInterpolator(new DecelerateInterpolator());
mBoomEndAnimator.setDuration(7000);
mBoomEndAnimator.addUpdateListener(valueAnimator -> mFirework.alpha = (int) valueAnimator.getAnimatedValue());
mBoomEndAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
isDisappear = true;
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
if (isDisappear) mLauncherRiseAnimator.start();
return true;
}
//绘制元素
private static class DrawView {
int top;
int left;
Bitmap bitmap;
float scale;
int alpha;
}
//绘制子线程
private static class DrawTask implements Runnable {
private final SurfaceHolder holder;
private final FireworkShow fireworkShow;
public DrawTask(SurfaceHolder holder, FireworkShow fireworkShow) {
this.holder = holder;
this.fireworkShow = fireworkShow;
}
@Override
public void run() {
Canvas canvas = null;
try {
canvas = holder.lockCanvas();
synchronized (holder) {
fireworkShow.draw(canvas);
}
} finally {
if (canvas != null) {
holder.unlockCanvasAndPost(canvas);
}
}
}
}
}
activity_main.xml引用:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.jon.firework.widget.FireworkShow
android:id="@+id/fs"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity使用:
public class MainActivity extends AppCompatActivity {
private FireworkShow fs;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fs = findViewById(R.id.fs);
fs.startFireworkShow();
}
@Override
protected void onDestroy() {
super.onDestroy();
fs.stopFireworkShow();
}
}