烟花效果
实现原理
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;//获取Looperprivate static final HandlerThread mHandlerThread = new HandlerThread(FireworkShow.class.getName());static {mHandlerThread.start();}//Handler消息处理private final Handler.Callback mCallback = new Handler.Callback() {@Overridepublic boolean handleMessage(@NonNull Message message) {if (message.what == MSG_DRAW) {mHandler.post(mDrawTask);mHandler.sendEmptyMessageDelayed(MSG_DRAW, 10);}return true;}};//用Looper创建弱引用Handlerprivate 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);}@Overridepublic 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();}@Overridepublic void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {if (mDrawTask == null) {mDrawTask = new DrawTask(surfaceHolder, this);}}@Overridepublic void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int format, int width, int height) {this.mWidth = width;this.mHeight = height;}@Overridepublic void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {}@Overridepublic 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() {@Overridepublic void onAnimationStart(Animator animator) {isDisappear = false;//烟花不显示mFirework.alpha = 0;//引信发射mLauncherView.alpha = 255;mLauncherAlphaAnimator.start();}@Overridepublic void onAnimationEnd(Animator animator) {//记录引信消失的位置lx = mLauncherView.left;ly = mLauncherView.top;}@Overridepublic void onAnimationCancel(Animator animator) {}@Overridepublic 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() {@Overridepublic void onAnimationStart(Animator animator) {}@Overridepublic void onAnimationEnd(Animator animator) {mLauncherView.alpha = 0; //引信消失//烟花显示mBoomAnimator.start();}@Overridepublic void onAnimationCancel(Animator animator) {}@Overridepublic 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() {@Overridepublic void onAnimationStart(Animator animator) {//烟花消失效果mBoomEndAnimator.start();}@Overridepublic void onAnimationEnd(Animator animator) {}@Overridepublic void onAnimationCancel(Animator animator) {}@Overridepublic 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() {@Overridepublic void onAnimationStart(Animator animator) {}@Overridepublic void onAnimationEnd(Animator animator) {isDisappear = true;}@Overridepublic void onAnimationCancel(Animator animator) {}@Overridepublic void onAnimationRepeat(Animator animator) {}});}@SuppressLint("ClickableViewAccessibility")@Overridepublic 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;}@Overridepublic 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="/apk/res/android"xmlns:app="/apk/res-auto"xmlns:tools="/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><com.jon.firework.widget.FireworkShowandroid: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;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);fs = findViewById(R.id.fs);fs.startFireworkShow();}@Overrideprotected void onDestroy() {super.onDestroy();fs.stopFireworkShow();}}