Android自定义SurfaceView简单实现烟花效果

Posted Steven Jon

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android自定义SurfaceView简单实现烟花效果相关的知识,希望对你有一定的参考价值。

烟花效果

实现原理

  • 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();
    }
}

以上是关于Android自定义SurfaceView简单实现烟花效果的主要内容,如果未能解决你的问题,请参考以下文章

Android自定义SurfaceView简单实现烟花效果

Android自定义SurfaceView简单实现烟花效果

讲讲Android为自定义view提供的SurfaceView

讲讲Android为自定义view提供的SurfaceView

Android 开发 SurfaceView 总结

android自定义相机拉伸surfaceview