设为首页收藏本站
网站公告 | 这是第一条公告
     

 找回密码
 立即注册
缓存时间17 现在时间17 缓存数据 这个世界上很多东西会变,很多人会走。 但你在我心里,从开始的那一天,到现在从来没有变过。 我一直在等,等你的消息。

这个世界上很多东西会变,很多人会走。 但你在我心里,从开始的那一天,到现在从来没有变过。 我一直在等,等你的消息。 -- 盛夏的果实

查看: 950|回复: 2

基于Android实现烟花效果

[复制链接]

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
23
主题
21
精华
0
金钱
75
积分
44
注册时间
2023-9-30
最后登录
2025-5-31

发表于 2024-2-25 11:48:32 | 显示全部楼层 |阅读模式
目录


  • 一、效果预览
  • 二、实现
  • 三、全部代码
  • 四、总结

一、效果预览

无中心版本
1.webp

有中心版本
2.webp


二、实现

2.1 均匀分布
我们首要解决的问题是计算出粒子运动方向,保证粒子能正常扩散到目标范围和区域,另外还有保证粒子尽可能随机和均匀分布在任意方向。
3.webp

方法是:
粒子扩散的范围是一个圆的范围内,我们要尽可能利用圆的旋转半径和夹角之间的关系,属于高中数学知识。另外也要控制粒子的数量,防止堆叠过多的问题。
  1. int t = i % 12;  
  2. double degree = random.nextFloat() * 30 + t * 30;  // 12等分圆,没等分都保证产生粒子
  3. // 360 /12 = 30 ,意味着每等分30度区域内需要产生一定的粒子
复制代码
2.2 速度计算
我们上一篇说过,计算出速度是最难的,要结合场景,这里我们采样计算终点的方式,目的有2个,限制粒子运动出大圆,限制时间。
  1. float minRadius = maxRadius * 1f / 2f;
  2. double radians = Math.toRadians(degree);
  3. int radius = (int) (random.nextFloat() * maxRadius / 2f);
  4. float x = (float) (Math.cos(radians) * (radius + minRadius));
  5. float y = (float) (Math.sin(radians) * (radius + minRadius));
  6. float speedX = (x - 0) / dt;
  7. float speedY = (y - 0) / dt;
复制代码
2.3 颜色
颜色选择自己喜欢的就可以,我喜欢五彩缤纷,所以随机生成
  1. int color = argb(random.nextFloat(), random.nextFloat(), random.nextFloat());
复制代码
2.4 定义粒子对象
  1.     static class Star {
  2.         private final boolean fromCenter;
  3.         private final int color;
  4.         private double radians;
  5.         private float r;
  6.         float speedX;
  7.         float speedY;
  8.         long startTime;
  9.         Path path = new Path();
  10.         int type = TYPE_QUAD;


  11.         public Star(float speedX, float speedY, long clockTime, float r, double radians, int color, boolean fromCenter, int type) {
  12.             this.speedX = speedX;
  13.             this.speedY = speedY;
  14.             this.startTime = clockTime;
  15.             this.r = r;
  16.             this.radians = radians;
  17.             this.fromCenter = fromCenter;
  18.             this.color = color;
  19.             this.type = type;
  20.         }
  21.         public void draw(Canvas canvas,Paint paint,long clockTime){

  22.       }
  23. }
复制代码
2.4 基础骨架
  1.         public void drawBase(Canvas canvas, Paint paint, long clockTime) {
  2.             long costTime = clockTime - startTime;

  3.             float dx = speedX * costTime;
  4.             float dy = speedY * costTime;

  5.             double currentRadius = Math.sqrt(dx * dx + dy * dy);

  6.             paint.setColor(color);

  7.             if (currentRadius > 0) {
  8.                 double asin = Math.asin(r / currentRadius);
  9.                 //利用反三角函数计算出切线与圆的夹角
  10.                 int t = 1;
  11.                 for (int i = 0; i < 2; i++) {
  12.                     double aspectRadius = Math.abs(Math.cos(asin) * currentRadius);  //切线长度
  13.                     float ax = (float) (aspectRadius * Math.cos(radians + asin * t));
  14.                     float ay = (float) (aspectRadius * Math.sin(radians + asin * t));
  15.                     if (fromCenter) {
  16.                         canvas.drawLine(0, 0, ax, ay, paint);
  17.                     } else {
  18.                         canvas.drawLine(dx / 3, dy / 3, ax, ay, paint);
  19.                     }
  20.                     t = -1;
  21.                 }

  22.             }
  23.             canvas.drawCircle(dx, dy, r, paint);
  24.         }
复制代码
4.webp

2.5 进一步优化
  1. public void drawCircleCCW(Canvas canvas, Paint paint, long clockTime) {
  2.             long costTime = clockTime - startTime;

  3.             float dx = speedX * costTime;
  4.             float dy = speedY * costTime;

  5.             double currentRadius = Math.sqrt(dx * dx + dy * dy);
  6.             path.reset();

  7.             if (currentRadius > 0) {

  8.                 if (fromCenter) {
  9.                     path.moveTo(0, 0);
  10.                 } else {
  11.                     path.moveTo(dx / 3, dy / 3);
  12.                 }

  13.                 //1、利用反三角函数计算出小圆切线与所有小圆原点与(0,0)点的夹角
  14.                 double asin = Math.asin(r / currentRadius);

  15.                 //2、计算出切线长度
  16.                 double aspectRadius = Math.abs(Math.cos(asin) * currentRadius);

  17.                 float axLeft = (float) (aspectRadius * Math.cos(radians - asin));
  18.                 float ayLeft = (float) (aspectRadius * Math.sin(radians - asin));
  19.                 path.lineTo(axLeft, ayLeft);

  20.                 float axRight = (float) (aspectRadius * Math.cos(radians + asin));
  21.                 float ayRight = (float) (aspectRadius * Math.sin(radians + asin));
  22.                 path.lineTo(axRight, ayRight);
  23.                 path.addCircle(dx, dy, r, Path.Direction.CCW);

  24.             }
  25.             path.close();
  26.             paint.setColor(color);
  27.             canvas.drawPath(path, paint);
  28.         }
复制代码
5.webp

有点样子了,但是问题是,Path动画并没有和粒子圆点闭合,这样就会有问题,后续如果要使用Shader着色 (为啥要用Shader着色,主要是火焰效果很难画出来,还得借助一些其他工具),必然产生不均匀问题。为了实现开头的效果,最初是计算切线和小圆的夹角让Path闭合,但是计算量和难度太大了,直接使用贝塞尔曲线更省事。
  1.      public void drawQuad(Canvas canvas, Paint paint, long clockTime) {
  2.             long costTime = clockTime - startTime;

  3.             float dx = speedX * costTime;
  4.             float dy = speedY * costTime;

  5.             double currentRadius = Math.sqrt(dx * dx + dy * dy);
  6.             path.reset();

  7.             if (currentRadius > 0) {
  8.                 if (fromCenter) {
  9.                     path.moveTo(0, 0);
  10.                 } else {
  11.                     path.moveTo(dx / 3, dy / 3);
  12.                 }

  13.                 //1、利用反三角函数计算出小圆切线与所有小圆原点与(0,0)点的夹角
  14.                 double asin = Math.asin(r / currentRadius);

  15.                 //2、计算出切线长度
  16.                 double aspectRadius = Math.abs(Math.cos(asin) * currentRadius);

  17.                 float axLeft = (float) (aspectRadius * Math.cos(radians - asin));
  18.                 float ayLeft = (float) (aspectRadius * Math.sin(radians - asin));
  19.                 path.lineTo(axLeft, ayLeft);

  20.                 float axRight = (float) (aspectRadius * Math.cos(radians + asin));
  21.                 float ayRight = (float) (aspectRadius * Math.sin(radians + asin));

  22.                 float cx = (float) (Math.cos(radians) * (currentRadius + 2 * r));
  23.                 float cy = (float) (Math.sin(radians) * (currentRadius + 2 * r));
  24.                 //如果使用三角函数计算切线可能很复杂,这里使用贝塞尔曲线简化逻辑
  25.                 path.quadTo(cx, cy, axRight, ayRight);
  26.                 path.lineTo(axRight, ayRight);

  27.             }
  28.             path.close();
  29.             paint.setColor(color);
  30.             canvas.drawPath(path, paint);
  31.         }
复制代码
三、全部代码
  1. public class FireworksView extends View implements Runnable {    private static final long V_SYNC_TIME = 30;    private final DisplayMetrics mDM;    private TextPaint mArcPaint;    private long displayTime = 500L; //控制时间,防止逃出边界    private long clockTime = 0;    private boolean isNextDrawingTimeScheduled = false;    private TextPaint mDrawerPaint = null;    private Random random;    final int maxStartNum = 50;    Star[] stars = new Star[maxStartNum];    private boolean isRefresh = true;    public static final int TYPE_BASE = 1;    public static final int TYPE_QUAD = 2;    public static final int TYPE_RECT = 3;    public static final int TYPE_CIRCLE_CCW = 4;    public FireworksView(Context context) {        this(context, null);    }    public FireworksView(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public FireworksView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        mDM = getResources().getDisplayMetrics();        initPaint();        setOnClickListener(new OnClickListener() {            @Override            public void onClick(View v) {                startPlay();            }        });    }    public static int argb(float red, float green, float blue) {        return ((int) (1 * 255.0f + 0.5f) << 24) |                ((int) (red * 255.0f + 0.5f) << 16) |                ((int) (green * 255.0f + 0.5f) << 8) |                (int) (blue * 255.0f + 0.5f);    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        int widthMode = MeasureSpec.getMode(widthMeasureSpec);        int widthSize = MeasureSpec.getSize(widthMeasureSpec);        if (widthMode != MeasureSpec.EXACTLY) {            widthSize = mDM.widthPixels / 2;        }        int heightMode = MeasureSpec.getMode(heightMeasureSpec);        int heightSize = MeasureSpec.getSize(heightMeasureSpec);        if (heightMode != MeasureSpec.EXACTLY) {            heightSize = widthSize / 2;        }        random = new Random(SystemClock.uptimeMillis());        setMeasuredDimension(widthSize, heightSize);    }    public float dp2px(float dp) {        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, mDM);    }    public float sp2px(float dp) {        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, dp, mDM);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        int width = getWidth();        int height = getHeight();        if (width <= 10 || height <= 10) {            return;        }        int saveCount = canvas.save();        int maxRadius = Math.min(width, height) / 2;        canvas.translate(width / 2, height / 2);        long clockTime = getClockTime();        if (isRefresh) {            float dt = 1000;            float r = 5;            for (int i = 0; i < maxStartNum; i++) {                int t = i % 12;                double degree = random.nextFloat() * 30 + t * 30;  // 12等分圆                float minRadius = maxRadius * 1f / 2f;                double radians = Math.toRadians(degree);                int radius = (int) (random.nextFloat() * maxRadius / 2f);                float x = (float) (Math.cos(radians) * (radius + minRadius));                float y = (float) (Math.sin(radians) * (radius + minRadius));                float speedX = (x - 0) / dt;                float speedY = (y - 0) / dt;                int color = argb(random.nextFloat(), random.nextFloat(), random.nextFloat());                stars[i] = new Star(speedX, speedY, clockTime, r, radians, color, false, TYPE_QUAD);            }            isRefresh = false;        }        for (int i = 0; i < maxStartNum; i++) {            Star star = stars[i];            star.draw(canvas, mDrawerPaint, clockTime);        }        if (!isNextDrawingTimeScheduled) {            isNextDrawingTimeScheduled = true;            postDelayed(this, V_SYNC_TIME);        }        canvas.restoreToCount(saveCount);    }    @Override    public void run() {        isNextDrawingTimeScheduled = false;        clockTime += 32;        if (clockTime > displayTime) {            clockTime = displayTime;        }        postInvalidate();    }    private long getClockTime() {        return clockTime;    }    public void startPlay() {        clockTime = 0;        isRefresh = true;        removeCallbacks(this);        run();    }    private void initPaint() {        // 实例化画笔并打开抗锯齿        mArcPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);        mArcPaint.setAntiAlias(true);        mArcPaint.setStyle(Paint.Style.STROKE);        mArcPaint.setStrokeCap(Paint.Cap.ROUND);        mDrawerPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);        mDrawerPaint.setAntiAlias(true);        mDrawerPaint.setStyle(Paint.Style.FILL);        mDrawerPaint.setStrokeCap(Paint.Cap.ROUND);    }    static class Star {        private final boolean fromCenter;        private final int color;        private double radians;        private float r;        float speedX;        float speedY;        long startTime;        Path path = new Path();        int type = TYPE_QUAD;        public Star(float speedX, float speedY, long clockTime, float r, double radians, int color, boolean fromCenter, int type) {            this.speedX = speedX;            this.speedY = speedY;            this.startTime = clockTime;            this.r = r;            this.radians = radians;            this.fromCenter = fromCenter;            this.color = color;            this.type = type;        }        public void draw(Canvas canvas, Paint paint, long clockTime) {            switch (type) {                case TYPE_BASE:                    drawBase(canvas, paint, clockTime);                    break;                case TYPE_RECT:                    drawRect(canvas, paint, clockTime);                    break;                case TYPE_CIRCLE_CCW:                    drawCircleCCW(canvas, paint, clockTime);                    break;                case TYPE_QUAD:                    drawQuad(canvas, paint, clockTime);                    break;            }        }        public void drawQuad(Canvas canvas, Paint paint, long clockTime) {
  2.             long costTime = clockTime - startTime;

  3.             float dx = speedX * costTime;
  4.             float dy = speedY * costTime;

  5.             double currentRadius = Math.sqrt(dx * dx + dy * dy);
  6.             path.reset();

  7.             if (currentRadius > 0) {
  8.                 if (fromCenter) {
  9.                     path.moveTo(0, 0);
  10.                 } else {
  11.                     path.moveTo(dx / 3, dy / 3);
  12.                 }

  13.                 //1、利用反三角函数计算出小圆切线与所有小圆原点与(0,0)点的夹角
  14.                 double asin = Math.asin(r / currentRadius);

  15.                 //2、计算出切线长度
  16.                 double aspectRadius = Math.abs(Math.cos(asin) * currentRadius);

  17.                 float axLeft = (float) (aspectRadius * Math.cos(radians - asin));
  18.                 float ayLeft = (float) (aspectRadius * Math.sin(radians - asin));
  19.                 path.lineTo(axLeft, ayLeft);

  20.                 float axRight = (float) (aspectRadius * Math.cos(radians + asin));
  21.                 float ayRight = (float) (aspectRadius * Math.sin(radians + asin));

  22.                 float cx = (float) (Math.cos(radians) * (currentRadius + 2 * r));
  23.                 float cy = (float) (Math.sin(radians) * (currentRadius + 2 * r));
  24.                 //如果使用三角函数计算切线可能很复杂,这里使用贝塞尔曲线简化逻辑
  25.                 path.quadTo(cx, cy, axRight, ayRight);
  26.                 path.lineTo(axRight, ayRight);

  27.             }
  28.             path.close();
  29.             paint.setColor(color);
  30.             canvas.drawPath(path, paint);
  31.         }        public void drawCircleCCW(Canvas canvas, Paint paint, long clockTime) {
  32.             long costTime = clockTime - startTime;

  33.             float dx = speedX * costTime;
  34.             float dy = speedY * costTime;

  35.             double currentRadius = Math.sqrt(dx * dx + dy * dy);
  36.             path.reset();

  37.             if (currentRadius > 0) {

  38.                 if (fromCenter) {
  39.                     path.moveTo(0, 0);
  40.                 } else {
  41.                     path.moveTo(dx / 3, dy / 3);
  42.                 }

  43.                 //1、利用反三角函数计算出小圆切线与所有小圆原点与(0,0)点的夹角
  44.                 double asin = Math.asin(r / currentRadius);

  45.                 //2、计算出切线长度
  46.                 double aspectRadius = Math.abs(Math.cos(asin) * currentRadius);

  47.                 float axLeft = (float) (aspectRadius * Math.cos(radians - asin));
  48.                 float ayLeft = (float) (aspectRadius * Math.sin(radians - asin));
  49.                 path.lineTo(axLeft, ayLeft);

  50.                 float axRight = (float) (aspectRadius * Math.cos(radians + asin));
  51.                 float ayRight = (float) (aspectRadius * Math.sin(radians + asin));
  52.                 path.lineTo(axRight, ayRight);
  53.                 path.addCircle(dx, dy, r, Path.Direction.CCW);

  54.             }
  55.             path.close();
  56.             paint.setColor(color);
  57.             canvas.drawPath(path, paint);
  58.         }        public void drawBase(Canvas canvas, Paint paint, long clockTime) {
  59.             long costTime = clockTime - startTime;

  60.             float dx = speedX * costTime;
  61.             float dy = speedY * costTime;

  62.             double currentRadius = Math.sqrt(dx * dx + dy * dy);

  63.             paint.setColor(color);

  64.             if (currentRadius > 0) {
  65.                 double asin = Math.asin(r / currentRadius);
  66.                 //利用反三角函数计算出切线与圆的夹角
  67.                 int t = 1;
  68.                 for (int i = 0; i < 2; i++) {
  69.                     double aspectRadius = Math.abs(Math.cos(asin) * currentRadius);  //切线长度
  70.                     float ax = (float) (aspectRadius * Math.cos(radians + asin * t));
  71.                     float ay = (float) (aspectRadius * Math.sin(radians + asin * t));
  72.                     if (fromCenter) {
  73.                         canvas.drawLine(0, 0, ax, ay, paint);
  74.                     } else {
  75.                         canvas.drawLine(dx / 3, dy / 3, ax, ay, paint);
  76.                     }
  77.                     t = -1;
  78.                 }

  79.             }
  80.             canvas.drawCircle(dx, dy, r, paint);
  81.         }        public void drawRect(Canvas canvas, Paint paint, long clockTime) {            long costTime = clockTime - startTime;            float dx = speedX * costTime;            float dy = speedY * costTime;            paint.setColor(color);            RectF rectF = new RectF(dx - r, dy - r, dx + r, dy + r);            canvas.drawRect(rectF, paint);         //   canvas.drawCircle(dx,dy,r,paint);        }    }}
复制代码
四、总结

本篇我们大量使用了三角函数、反三角函数,因此一定要掌握好数学基础。
以上就是基于Android实现烟花效果的详细内容,更多关于Android烟花效果的资料请关注晓枫资讯其它相关文章!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
晓枫资讯-科技资讯社区-免责声明
免责声明:以上内容为本网站转自其它媒体,相关信息仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同其观点或证实其内容的真实性。
      1、注册用户在本社区发表、转载的任何作品仅代表其个人观点,不代表本社区认同其观点。
      2、管理员及版主有权在不事先通知或不经作者准许的情况下删除其在本社区所发表的文章。
      3、本社区的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,举报反馈:点击这里给我发消息进行删除处理。
      4、本社区一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
      5、以上声明内容的最终解释权归《晓枫资讯-科技资讯社区》所有。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
20
积分
20
注册时间
2022-12-25
最后登录
2022-12-25

发表于 2024-5-24 10:24:40 | 显示全部楼层
顶顶更健康!!!
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
21
积分
22
注册时间
2022-12-29
最后登录
2022-12-29

发表于 2025-3-8 15:23:52 | 显示全部楼层
路过,支持一下
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~
严禁发布广告,淫秽、色情、赌博、暴力、凶杀、恐怖、间谍及其他违反国家法律法规的内容。!晓枫资讯-社区
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

1楼
2楼
3楼

手机版|晓枫资讯--科技资讯社区 本站已运行

CopyRight © 2022-2025 晓枫资讯--科技资讯社区 ( BBS.yzwlo.com ) . All Rights Reserved .

晓枫资讯--科技资讯社区

本站内容由用户自主分享和转载自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。

如有侵权、违反国家法律政策行为,请联系我们,我们会第一时间及时清除和处理! 举报反馈邮箱:点击这里给我发消息

Powered by Discuz! X3.5

快速回复 返回顶部 返回列表