当前位置: 代码网 > it编程>App开发>Android > 基于Android实现烟花效果

基于Android实现烟花效果

2024年05月19日 Android 我要评论
一、效果预览无中心版本有中心版本二、实现2.1 均匀分布我们首要解决的问题是计算出粒子运动方向,保证粒子能正常扩散到目标范围和区域,另外还有保证粒子尽可能随机和均匀分布在任意方向。方法是:粒子扩散的范

一、效果预览

无中心版本

有中心版本

二、实现

2.1 均匀分布

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

方法是:

粒子扩散的范围是一个圆的范围内,我们要尽可能利用圆的旋转半径和夹角之间的关系,属于高中数学知识。另外也要控制粒子的数量,防止堆叠过多的问题。

int t = i % 12;  
double degree = random.nextfloat() * 30 + t * 30;  // 12等分圆,没等分都保证产生粒子 
// 360 /12 = 30 ,意味着每等分30度区域内需要产生一定的粒子

2.2 速度计算

我们上一篇说过,计算出速度是最难的,要结合场景,这里我们采样计算终点的方式,目的有2个,限制粒子运动出大圆,限制时间。

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;

2.3 颜色

颜色选择自己喜欢的就可以,我喜欢五彩缤纷,所以随机生成

int color = argb(random.nextfloat(), random.nextfloat(), random.nextfloat());

2.4 定义粒子对象

    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){

      }
}

2.4 基础骨架

        public void drawbase(canvas canvas, paint paint, long clocktime) {
            long costtime = clocktime - starttime;

            float dx = speedx * costtime;
            float dy = speedy * costtime;

            double currentradius = math.sqrt(dx * dx + dy * dy);

            paint.setcolor(color);

            if (currentradius > 0) {
                double asin = math.asin(r / currentradius);
                //利用反三角函数计算出切线与圆的夹角
                int t = 1;
                for (int i = 0; i < 2; i++) {
                    double aspectradius = math.abs(math.cos(asin) * currentradius);  //切线长度
                    float ax = (float) (aspectradius * math.cos(radians + asin * t));
                    float ay = (float) (aspectradius * math.sin(radians + asin * t));
                    if (fromcenter) {
                        canvas.drawline(0, 0, ax, ay, paint);
                    } else {
                        canvas.drawline(dx / 3, dy / 3, ax, ay, paint);
                    }
                    t = -1;
                }

            }
            canvas.drawcircle(dx, dy, r, paint);
        }

2.5 进一步优化

public void drawcircleccw(canvas canvas, paint paint, long clocktime) {
            long costtime = clocktime - starttime;

            float dx = speedx * costtime;
            float dy = speedy * costtime;

            double currentradius = math.sqrt(dx * dx + dy * dy);
            path.reset();

            if (currentradius > 0) {

                if (fromcenter) {
                    path.moveto(0, 0);
                } else {
                    path.moveto(dx / 3, dy / 3);
                }

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

                //2、计算出切线长度
                double aspectradius = math.abs(math.cos(asin) * currentradius);

                float axleft = (float) (aspectradius * math.cos(radians - asin));
                float ayleft = (float) (aspectradius * math.sin(radians - asin));
                path.lineto(axleft, ayleft);

                float axright = (float) (aspectradius * math.cos(radians + asin));
                float ayright = (float) (aspectradius * math.sin(radians + asin));
                path.lineto(axright, ayright);
                path.addcircle(dx, dy, r, path.direction.ccw);

            }
            path.close();
            paint.setcolor(color);
            canvas.drawpath(path, paint);
        }

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

     public void drawquad(canvas canvas, paint paint, long clocktime) {
            long costtime = clocktime - starttime;

            float dx = speedx * costtime;
            float dy = speedy * costtime;

            double currentradius = math.sqrt(dx * dx + dy * dy);
            path.reset();

            if (currentradius > 0) {
                if (fromcenter) {
                    path.moveto(0, 0);
                } else {
                    path.moveto(dx / 3, dy / 3);
                }

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

                //2、计算出切线长度
                double aspectradius = math.abs(math.cos(asin) * currentradius);

                float axleft = (float) (aspectradius * math.cos(radians - asin));
                float ayleft = (float) (aspectradius * math.sin(radians - asin));
                path.lineto(axleft, ayleft);

                float axright = (float) (aspectradius * math.cos(radians + asin));
                float ayright = (float) (aspectradius * math.sin(radians + asin));

                float cx = (float) (math.cos(radians) * (currentradius + 2 * r));
                float cy = (float) (math.sin(radians) * (currentradius + 2 * r));
                //如果使用三角函数计算切线可能很复杂,这里使用贝塞尔曲线简化逻辑
                path.quadto(cx, cy, axright, ayright);
                path.lineto(axright, ayright);

            }
            path.close();
            paint.setcolor(color);
            canvas.drawpath(path, paint);
        }

三、全部代码

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) {
            long costtime = clocktime - starttime;

            float dx = speedx * costtime;
            float dy = speedy * costtime;

            double currentradius = math.sqrt(dx * dx + dy * dy);
            path.reset();

            if (currentradius > 0) {
                if (fromcenter) {
                    path.moveto(0, 0);
                } else {
                    path.moveto(dx / 3, dy / 3);
                }

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

                //2、计算出切线长度
                double aspectradius = math.abs(math.cos(asin) * currentradius);

                float axleft = (float) (aspectradius * math.cos(radians - asin));
                float ayleft = (float) (aspectradius * math.sin(radians - asin));
                path.lineto(axleft, ayleft);

                float axright = (float) (aspectradius * math.cos(radians + asin));
                float ayright = (float) (aspectradius * math.sin(radians + asin));

                float cx = (float) (math.cos(radians) * (currentradius + 2 * r));
                float cy = (float) (math.sin(radians) * (currentradius + 2 * r));
                //如果使用三角函数计算切线可能很复杂,这里使用贝塞尔曲线简化逻辑
                path.quadto(cx, cy, axright, ayright);
                path.lineto(axright, ayright);

            }
            path.close();
            paint.setcolor(color);
            canvas.drawpath(path, paint);
        }

        public void drawcircleccw(canvas canvas, paint paint, long clocktime) {
            long costtime = clocktime - starttime;

            float dx = speedx * costtime;
            float dy = speedy * costtime;

            double currentradius = math.sqrt(dx * dx + dy * dy);
            path.reset();

            if (currentradius > 0) {

                if (fromcenter) {
                    path.moveto(0, 0);
                } else {
                    path.moveto(dx / 3, dy / 3);
                }

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

                //2、计算出切线长度
                double aspectradius = math.abs(math.cos(asin) * currentradius);

                float axleft = (float) (aspectradius * math.cos(radians - asin));
                float ayleft = (float) (aspectradius * math.sin(radians - asin));
                path.lineto(axleft, ayleft);

                float axright = (float) (aspectradius * math.cos(radians + asin));
                float ayright = (float) (aspectradius * math.sin(radians + asin));
                path.lineto(axright, ayright);
                path.addcircle(dx, dy, r, path.direction.ccw);

            }
            path.close();
            paint.setcolor(color);
            canvas.drawpath(path, paint);
        }


        public void drawbase(canvas canvas, paint paint, long clocktime) {
            long costtime = clocktime - starttime;

            float dx = speedx * costtime;
            float dy = speedy * costtime;

            double currentradius = math.sqrt(dx * dx + dy * dy);

            paint.setcolor(color);

            if (currentradius > 0) {
                double asin = math.asin(r / currentradius);
                //利用反三角函数计算出切线与圆的夹角
                int t = 1;
                for (int i = 0; i < 2; i++) {
                    double aspectradius = math.abs(math.cos(asin) * currentradius);  //切线长度
                    float ax = (float) (aspectradius * math.cos(radians + asin * t));
                    float ay = (float) (aspectradius * math.sin(radians + asin * t));
                    if (fromcenter) {
                        canvas.drawline(0, 0, ax, ay, paint);
                    } else {
                        canvas.drawline(dx / 3, dy / 3, ax, ay, paint);
                    }
                    t = -1;
                }

            }
            canvas.drawcircle(dx, dy, r, paint);
        }

        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烟花效果的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com