Android Compose打造仿现实逼真的烟花特效

首先捋一下实现仿现实烟花特效的核心思路,主要通过模拟烟花的完整生命周期(发射、上升、爆炸、粒子消散),结合物理运动规律(重力、阻力)和视觉效果增强,实现贴近真实的烟花体验。以下是动态效果图:

下面开始具体的实现过程:

一、数据模型设计

kotlin 复制代码
// 粒子数据类,存储粒子的物理属性
data class Particle(
    var x: Float,
    var y: Float,
    var velocityX: Float,
    var velocityY: Float,
    var radius: Float,
    var color: Color,
    var life: Float,
    var maxLife: Float,
    var rotation: Float,
    var rotationSpeed: Float
)

// 花炮数据类
data class Firecracker(
    val id: Int,
    var x: Float,
    var y: Float,
    val targetY: Float, // 目标高度,到达后自动爆炸
    var isExploding: Boolean = false,
    var isFinished: Boolean = false,
    val color: Color,
    var trail: List<Offset> = emptyList() // 花炮尾迹
)

通过数据类封装烟花和粒子的核心属性,为物理模拟和绘制提供数据基础:

  • Particle(粒子) :存储爆炸后粒子的物理状态,包括位置(x/y)、速度(velocityX/velocityY)、大小(radius)、颜色(color)、生命周期(life/maxLife)、旋转状态(rotation/rotationSpeed)。
  • Firecracker(烟花弹) :存储未爆炸时烟花的状态,包括位置(x/y)、目标爆炸高度(targetY)、爆炸状态(isExploding)、是否结束(isFinished)、颜色(color)和上升尾迹(trail)。

二、烟花生命周期管理

通过多个协程(LaunchedEffect)分别处理烟花从生成到消散的完整流程:

1. 烟花弹生成

scss 复制代码
    // 定时生成新的花炮
    LaunchedEffect(Unit) {
        while (isActive) {
            val times = Random.nextInt(4, 10)
            repeat(times) {
                // 随机水平位置生成新花炮
                val x = Random.nextFloat() * (width - 200.dp.value) + 100.dp.value
                // 随机目标高度
                val targetY = Random.nextFloat() * (height / 3) + height / 4
                fireworks = fireworks + Firecracker(
                    id = nextFirecrackerId++,
                    x = x,
                    y = height - 200.dp.value, // 从底部开始
                    targetY = targetY,
                    color = when (Random.nextInt(6)) {
                        0 -> Color(0xFFFF5252) // 红
                        1 -> Color(0xFFFFEB3B) // 黄
                        2 -> Color(0xFF4CAF50) // 绿
                        3 -> Color(0xFF2196F3) // 蓝
                        4 -> Color(0xFF9C27B0) // 紫
                        else -> Color(0xFFFF9800) // 橙
                    }
                )
                delay(300)
            }
            
            // 过滤掉已完成的花炮
            fireworks = fireworks.filter { !it.isFinished }
            
            delay(3000) // 每几秒生成多个新花炮
        }
    }
  • 定时(每3秒)在随机水平位置(x)生成多个新烟花弹,初始位置从底部开始,目标爆炸高度(targetY)随机,颜色随机选取红、黄、绿等6种颜色。
  • 自动过滤已完成生命周期的烟花弹,避免内存累积。

2. 烟花弹上升动画

ini 复制代码
    // 更新花炮位置(上升动画)
    LaunchedEffect(fireworks) {
        while (isActive) {
            fireworks = fireworks.map { firecracker ->
                if (firecracker.isExploding || firecracker.isFinished) {
                    return@map firecracker
                }
                
                // 花炮上升,速度逐渐减慢(模拟重力影响)
                val progress = 1 - (firecracker.y / height * 1.1f)
                val speed = 8 * (1 - progress * 0.8f)
                var newY = firecracker.y - speed
                
                // 更新尾迹
                val newTrail = (listOf(Offset(firecracker.x, firecracker.y)) + firecracker.trail)
                    .take(30) // 只保留最近的30个点
                
                // 如果到达目标高度,自动爆炸
                if (newY <= firecracker.targetY) {
                    newY = firecracker.targetY
                    // 创建爆炸效果
                    val explosion = createExplosion(
                        centerX = firecracker.x,
                        centerY = newY,
                        particleCount = particleCount,
                        minSize = minParticleSize,
                        maxSize = maxParticleSize,
                        baseColor = firecracker.color
                    )
                    explosions = explosions + listOf(explosion)
                    return@map firecracker.copy(
                        y = newY,
                        isExploding = true,
                        trail = newTrail
                    )
                }
                
                firecracker.copy(y = newY, trail = newTrail)
            }
            
            delay(16) // 约60fps
        }
    }
  • 实时更新烟花弹位置:上升速度随高度增加而减慢(模拟重力影响),计算公式为 speed = 8f * (1 - progress * 0.8f)progress 为上升进度)。
  • 同步更新尾迹:保留最近30个位置点,形成逐渐消失的轨迹效果。
  • 触发爆炸条件:当烟花弹到达目标高度(y <= targetY)时,生成爆炸粒子并标记为爆炸状态(isExploding

3. 爆炸粒子物理模拟

arduino 复制代码
    // 更新爆炸粒子状态
    LaunchedEffect(explosions) {
        while (isActive) {
            // 更新所有粒子的物理状态
            explosions = explosions.map { particles ->
                particles.mapNotNull { particle ->
                    // 更新粒子生命
                    particle.life -= 0.015f
                    if (particle.life <= 0) {
                        return@mapNotNull null
                    }

                    // 应用重力 (模拟向下的加速度)
                    particle.velocityY += 0.15f

                    // 应用空气阻力 (速度逐渐减慢)
                    particle.velocityX *= 0.98f
                    particle.velocityY *= 0.98f

                    // 更新位置
                    particle.x += particle.velocityX
                    particle.y += particle.velocityY

                    // 更新旋转
                    particle.rotation += particle.rotationSpeed

                    particle
                }
            }.filter { it.isNotEmpty() } // 过滤掉已经没有粒子的爆炸
            
            // 标记已爆炸完成的花炮
            fireworks = fireworks.map { firecracker ->
                if (firecracker.isExploding && 
                    explosions.none { explosion -> 
                        explosion.any { it.x == firecracker.x && it.y == firecracker.y }
                    }) {
                    firecracker.copy(isFinished = true, isExploding = false)
                } else {
                    firecracker
                }
            }
            
            delay(16) // 约60fps
        }
    }
  • 粒子生命周期管理:每个粒子的life随时间减少(-0.015f/帧),生命周期结束后自动消失。
  • 物理运动模拟:
    • 重力:粒子竖直方向速度(velocityY)持续增加(+0.15f/帧),模拟向下的加速度。
    • 空气阻力:水平和竖直速度均乘以0.98(*0.98f/帧),模拟速度衰减。
    • 位置更新:根据速度实时更新粒子坐标(x += velocityXy += velocityY)。
  • 旋转效果:粒子随机旋转速度(rotationSpeed),实时更新旋转角度(rotation),增强视觉动态感。

三、交互设计

ini 复制代码
    // 处理点击事件
    fun handleFirecrackerTap(x: Float, y: Float) {
        fireworks.forEachIndexed { index, firecracker ->
            if (!firecracker.isExploding && !firecracker.isFinished) {
                // 计算点击位置与花炮的距离
                val distance = sqrt(
                    (x - firecracker.x).pow(2) + (y - firecracker.y).pow(2)
                )
                
                // 如果点击在花炮附近
                if (distance < firecrackerSize * 8) {
                    // 创建爆炸效果
                    val explosion = createExplosion(
                        centerX = firecracker.x,
                        centerY = firecracker.y,
                        particleCount = particleCount,
                        minSize = minParticleSize,
                        maxSize = maxParticleSize,
                        baseColor = firecracker.color
                    )
                    explosions = explosions + listOf(explosion)
                    
                    // 更新花炮状态
                    fireworks = fireworks.mapIndexed { i, f ->
                        if (i == index) f.copy(isExploding = true) else f
                    }
                }
            }
        }
    }

    Box(
        modifier = modifier
            .fillMaxSize()
            .pointerInput(Unit) {
                detectTapGestures { tapOffset ->
                    handleFirecrackerTap(tapOffset.x, tapOffset.y)
                }
            }
    )

支持用户点击提前引爆烟花:

  • 通过detectTapGesture监听点击位置,计算点击点与烟花弹的距离。
  • 当距离小于烟花弹大小的8倍时,触发爆炸,生成粒子效果并标记烟花弹为爆炸状态。

四、爆炸粒子生成逻辑

scss 复制代码
// 创建一次爆炸的所有粒子,基于花炮颜色生成相近色
private fun createExplosion(
    centerX: Float,
    centerY: Float,
    particleCount: Int,
    minSize: Float,
    maxSize: Float,
    baseColor: Color? = null
): List<Particle> {
    return List(particleCount) {
        // 随机角度和速度
        val angle = Random.nextFloat() * 2 * Math.PI.toFloat()
        val speed = Random.nextFloat() * 6f + 2f
        
        // 计算速度分量
        val velocityX = cos(angle).toFloat() * speed
        val velocityY = sin(angle).toFloat() * speed
        
        // 随机大小
        val radius = Random.nextFloat() * (maxSize - minSize) + minSize
        
        // 颜色 - 基于基础颜色生成相近色
        val color = baseColor?.let {
            var hsv = floatArrayOf(0f, 0f, 0f)
            android.graphics.Color.colorToHSV(it.toArgb(), hsv)
            val hue = (hsv[0] + (Random.nextFloat() * 30f - 15f)).mod(360f)
            Color.hsv(hue, hsv[1], hsv[2])
        } ?: when (Random.nextInt(6)) {
            0 -> Color(0xFFFF5252) // 红
            1 -> Color(0xFFFFEB3B) // 黄
            2 -> Color(0xFF4CAF50) // 绿
            3 -> Color(0xFF2196F3) // 蓝
            4 -> Color(0xFF9C27B0) // 紫
            else -> Color(0xFFFF9800) // 橙
        }
        
        // 随机生命周期和旋转
        val maxLife = Random.nextFloat() * 0.5f + 1.0f
        
        Particle(
            x = centerX,
            y = centerY,
            velocityX = velocityX,
            velocityY = velocityY,
            radius = radius,
            color = color,
            life = maxLife,
            maxLife = maxLife,
            rotation = Random.nextFloat() * 360f,
            rotationSpeed = Random.nextFloat() * 10f - 5f
        )
    }
}

createExplosion函数负责生成爆炸时的粒子群:

  • 随机角度(0~2π)和速度(2~8f),确保粒子向四周扩散。
  • 随机大小(minParticleSize~maxParticleSize),模拟不同颗粒的爆炸效果。
  • 颜色基于烟花弹主色生成相近色(色调±15°范围内波动),保持视觉一致性;若无主色则随机选取预设颜色。

总结

该实现通过拆分烟花生命周期 (生成→上升→爆炸→消散),结合物理规律模拟 (重力、阻力、速度衰减)和分层视觉绘制 (背景、尾迹、主体、粒子),再加上用户交互触发,最终实现了贴近真实且具有交互性的烟花特效。

完整代码 请点击Fireworks查看,因技能水平有限,如有问题欢迎指正。

相关推荐
翻滚丷大头鱼3 小时前
android 性能优化—ANR
android·性能优化
翻滚丷大头鱼3 小时前
android 性能优化—内存泄漏,内存溢出OOM
android·性能优化
拜无忧3 小时前
【教程】flutter常用知识点总结-针对小白
android·flutter·android studio
拜无忧4 小时前
【教程】Flutter 高性能项目架构创建指南:从入门到高性能架构
android·flutter·android studio
用户2018792831674 小时前
故事:公司的 "私人储物柜" 系统(ThreadLocalMap)
android·java
醉过才知酒浓4 小时前
flutter 拦截返回按钮的方法(WillPopScope or PopScope)
flutter
CYRUS_STUDIO4 小时前
如何防止 so 文件被轻松逆向?精准控制符号导出 + JNI 动态注册
android·c++·安全
yinmaisoft4 小时前
当低代码遇上AI,有趣,实在有趣
android·人工智能·低代码·开发工具·rxjava
如此风景5 小时前
Compose Modifier 修饰符介绍
android