Android 粒子喷泉动效

前言:

在学习open gl es实现动效的时候,打算回顾了一下用普通的2D坐标系实现粒子效果和 open gl 3d 坐标系的区别,以及难易程度,因此本篇以Canvas 2D坐标系实现了一个简单的demo。

粒子动效原理:

粒子动效本质上是一种知道起点和各个坐标轴方向速度的无规则运动,这种动效的实现的算法确实有规则的。

我们以物理学公式为例,本质上是一种匀加速矢量方程,至于为什么忽快忽慢,也是从该类方程延伸出来的新算法。

x = startX + (Vx*t + 1/2*a*tX * t)

y = startY + (Vy * t + 1/2*aY*t * t)

当然,用向量解释就是向量A到向量B各个分量的运动学公式。

粒子动效的特点:

  1. 具备起点位置

  2. 需要计算出速度和运动角度,当然,难点也是速度的计算和定义。

  3. 符合运动学方程,但与现实规律有区别,因为在手机中使用的单位和重力加速度都是有一定区别的。

二、代码实现

2.1 构建粒子对象,在open gl中由于没有对象化的概念,绘制时通过数组的偏移实现,当然后果是代码可读性差一些。

ini 复制代码
public class Particle {
    private float speedZ = 0;
    private float x;
    private float y;

    private float speedX;
    private float speedY;

    int color;
    long startTime;
    private float radius = 10;

    public Particle(float x, float y, float speedX, float speedY, int color,float speedZ,long clockTime) {
        this.x = x;
        this.y = y;
        this.speedX = speedX;
        this.speedY = speedY;
        this.speedZ = speedZ;
        this.color = color;
        this.startTime = clockTime;
    }

    public void draw(Canvas canvas, long clockTime, Paint paint) {
        long costTime = (clockTime - startTime)/2;
        float gravityY = costTime * costTime / 3000f;  //重力加速度
        float dx = costTime * speedX;
        float dy = costTime * speedY + gravityY;
        float v = costTime / 500f;

        float ty = y + dy;  // vt + t*t/2*g
        float tx = x + dx;

        int paintColor = paint.getColor();
        if(v > 1f && speedZ != 1) {
           //非z轴正半轴的降低透明度
            int argb = argb((int) (Color.alpha(color) /v), Color.red(color), Color.green(color), Color.blue(color));
            paint.setColor(argb);
        }else {
            paint.setColor(color);
        }

        float tRadius = radius;
      //这只Blend叠加效果,这个api版本较高        paint.setBlendMode(BlendMode.DIFFERENCE);  

        canvas.drawCircle(tx,ty,tRadius,paint);
        paint.setColor(paintColor);

        if(ty > radius){
            reset(clockTime);
        }
    }
    private void reset(long clockTime) {
        startTime = clockTime;
    }
    public static int argb(
            @IntRange(from = 0, to = 255) int alpha,
            @IntRange(from = 0, to = 255) int red,
            @IntRange(from = 0, to = 255) int green,
            @IntRange(from = 0, to = 255) int blue) {
        return (alpha << 24) | (red << 16) | (green << 8) | blue;
    }
} 

2.2 构建粒子系统

ini 复制代码
public class CanvasParticleSystem {
    private Particle[] particles;
    private int maxParticleCount = 500;
    private Random random = new Random();
    private final float angle = 30f;  //x轴的活动范围
    private int index = 0;
    private float radius = 60;  //x轴和y轴不能超过的边界

    public void addParticle(float centerX,float centerY,float maxWidth,float maxHeight,long clockTime){

        if(particles == null){
            particles = new Particle[maxParticleCount];
        }
        if(index >= particles.length) {
            return;
        }

        float degree = (float) Math.toRadians((270 - angle) + 2f * angle * random.nextFloat());
        float dx = (float) (radius * Math.cos(degree)) * 2f;  //计算初目标位置x的随机点
        float dy = -(float) ((maxHeight * 1f / 2 - radius * 2f) * random.nextFloat()) - maxHeight / 2f;
        //计算目标y的随机点

         float dt = 1000;  //时间按1s计算
    //    dx = speedx * dt + centerX;
    //    dy = speedy * dt + centerY;

        float sx = (dx - centerX) / dt;  // x轴方向的速度
        float sy = (dy - centerY) / dt;   //y轴方向的速度

        int num = (int) (random.nextFloat() * 100);
        float sz = 0;
        if(num % 5 == 0) {
            sz = random.nextBoolean() ? -1 : 1;
        }

        int argb = argb(random.nextFloat(), random.nextFloat(), random.nextFloat());
       // argb = argb(210, 110, 80);
        Particle p = new Particle(centerX,centerY,sx,sy, argb,sz,clockTime);

        particles[index++] = p;
    }

    public void drawFrame(Canvas canvas, Paint paint,long clockTime) {
        for (int i = 0; i < particles.length;i++) {
            Particle particle = particles[i];
            if(particle == null) continue;
            particle.draw(canvas,clockTime,paint);
        }
    }

    public  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);
    }
}

2.3 粒子View实现

scss 复制代码
public class PracticeView extends View {

    Paint paint;
    CanvasParticleSystem particleSystem;
    private long clockTime = 0L; //自定义时钟,防止粒子堆积
    long startTimeout = 0;  

    public PracticeView(Context context) {
        super(context);
        init();
    }

    public PracticeView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public PracticeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    public void init(){
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setDither(false);
        paint.setStrokeWidth(2f);
        particleSystem = new CanvasParticleSystem();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int width = getWidth();
        int height = getHeight();
        if (width <= 10 || height <= 10) {
            return;
        }
        int save = canvas.save();
        canvas.translate(width/2,height);

        fillParticles(5,width, height);
        particleSystem.drawFrame(canvas,paint,getClockTime());
        canvas.restoreToCount(save);
        clockTime += 32;
        postInvalidateDelayed(16);
    }


    private void fillParticles(int size,int width, int height) {
        if(SystemClock.uptimeMillis() - startTimeout > 60) {
            for (int i = 0; i < size; i++) {
                particleSystem.addParticle(0, 0, width, height,getClockTime());
            }
            startTimeout = SystemClock.uptimeMillis();
        }
    }
    private long getClockTime() {
        return clockTime;
    }
}

三、总结

总体上使用Canvas 绘制高帧率的粒子动效,其对比open gl肯定有很多差距,甚至有一些天然缺陷比如Z轴的处理。当然,易用性肯定是Canvas 2D的优势了。

四、参考文章

Android 烟花效果实现

相关推荐
Web极客码1 分钟前
深入了解WordPress网站访客意图
服务器·前端·wordpress
幺风33 分钟前
Claude Code 源码分析 — Tool/MCP/Skill 可扩展工具系统
前端·javascript·ai编程
vjmap40 分钟前
唯杰地图CAD图层加高性能特效扩展包发布
前端·gis
ZC跨境爬虫44 分钟前
3D 地球卫星轨道可视化平台开发 Day7(AI异步加速+卫星系列精简+AI Agent自动评论)
前端·人工智能·3d·html·json
ID_180079054731 小时前
淘宝 API 上货 / 商品搬家 业务场景实现 + JSON 返回示例
前端·javascript·json
M ? A1 小时前
Vue 动态组件在 React 中,VuReact 会如何实现?
前端·javascript·vue.js·经验分享·react.js·面试·vureact
vipbic1 小时前
独立开发复盘:我用 Uni-app + Strapi v5 肝了一个“会上瘾”的打卡小程序
前端·微信小程序
IT_陈寒2 小时前
Vite的热更新突然失效,原来是因为这个配置
前端·人工智能·后端
ZC跨境爬虫3 小时前
3D 地球卫星轨道可视化平台开发 Day8(分步渲染200颗卫星+ 前端分页控制)
前端·python·3d·重构·html
竹林8183 小时前
RainbowKit快速集成多链钱包连接,我如何从“连不上”到“丝滑切换”
前端·javascript