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

一、项目概述与设计思路

在Android开发中,通过Jetpack Compose结合协程、Flow和Channel技术,可以实现高性能的粒子系统动画。本文将详细介绍如何构建一个完整的烟花动画效果,包含粒子系统模型、协程更新逻辑和Canvas渲染实现。

1.1 核心架构设计

粒子数据模型:粒子模型包含位置、速度、尺寸、透明度和生命周期属性,烟花模型则由多个粒子组成,并记录发射时间和颜色信息。每个烟花包含80-130个粒子,使用HSL色彩空间生成鲜艳颜色,粒子速度和大小随机化增强真实感。

协程控制架构:通过Channel接收控制命令,使用Flow处理命令流,协程实现60fps的粒子更新,确保动画流畅性。粒子更新包含速度衰减(0.98系数)和生命周期衰减,自动清理3秒后消失或生命周期耗尽的烟花。

二、数据模型与粒子系统实现

2.1 粒子数据类定义

首先定义粒子的核心属性,包括位置、速度、颜色、生命周期等:

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
)

粒子模型存储了粒子的物理属性,包括位置坐标(x,y)、速度分量(velocityX,velocityY)、大小(radius)、颜色(color)、生命周期(life/maxLife)以及旋转状态(rotation/rotationSpeed)。

2.2 烟花弹数据类

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() // 花炮尾迹
)

烟花弹模型记录未爆炸时的状态,包括当前位置(x,y)、目标爆炸高度(targetY)、爆炸状态(isExploding)、是否结束(isFinished)、颜色(color)和上升尾迹(trail)。

2.3 粒子生成逻辑

// 定时生成新的花炮
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种颜色。自动过滤已完成生命周期的烟花弹,避免内存累积。

三、烟花弹上升动画实现

3.1 位置更新逻辑

// 更新花炮位置(上升动画)
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)。

四、爆炸粒子物理模拟

4.1 粒子状态更新

// 更新爆炸粒子状态
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 += velocityX, y += velocityY)
  • 旋转效果:粒子随机旋转速度(rotationSpeed),实时更新旋转角度(rotation),增强视觉动态感

五、Canvas渲染实现

5.1 渲染优化技术

使用 BlendMode.Plus增强亮度叠加效果,粒子透明度随生命周期动态变化,实现平滑的消失效果。

5.2 绘制烟花弹

Canvas(modifier = Modifier.fillMaxSize()) {
    // 绘制烟花弹
    fireworks.forEach { firecracker ->
        if (!firecracker.isExploding && !firecracker.isFinished) {
            // 绘制烟花弹主体
            drawCircle(
                color = firecracker.color,
                center = Offset(firecracker.x, firecracker.y),
                radius = 8f
            )
            
            // 绘制尾迹
            if (firecracker.trail.isNotEmpty()) {
                drawPoints(
                    points = firecracker.trail,
                    pointMode = PointMode.Points,
                    color = firecracker.color.copy(alpha = 0.5f),
                    strokeWidth = 2f
                )
            }
        }
    }
    
    // 绘制爆炸粒子
    explosions.forEach { particles ->
        particles.forEach { particle ->
            drawCircle(
                color = particle.color.copy(alpha = particle.life / particle.maxLife),
                center = Offset(particle.x, particle.y),
                radius = particle.radius
            )
        }
    }
}

5.3 性能优化策略

优化技术 实现方式 效果
协程调度 Dispatchers.Default 避免阻塞UI线程
粒子过滤 filter生命周期 减少无效计算
批量渲染 Canvas单次绘制 降低GPU负载
内存复用 objectPool模式 减少对象创建

通过协程并行处理和Canvas批量渲染,在中端设备上可稳定支持200+粒子同时渲染。

六、交互设计

6.1 处理点击事件

// 处理点击事件
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 < 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, fc ->
                    if (i == index) fc.copy(isExploding = true) else fc
                }
            }
        }
    }
}

6.2 创建爆炸效果

private fun createExplosion(
    centerX: Float,
    centerY: Float,
    particleCount: Int,
    minSize: Float,
    maxSize: Float,
    baseColor: Color
): List<Particle> {
    return (0 until particleCount).map {
        val angle = Random.nextFloat() * 2 * PI.toFloat()
        val speed = Random.nextFloat() * 5 + 2
        val velocityX = cos(angle) * speed
        val velocityY = sin(angle) * speed
        
        Particle(
            x = centerX,
            y = centerY,
            velocityX = velocityX,
            velocityY = velocityY,
            radius = Random.nextFloat() * (maxSize - minSize) + minSize,
            color = baseColor.copy(
                red = (baseColor.red + Random.nextFloat() * 0.3f).coerceIn(0f, 1f),
                green = (baseColor.green + Random.nextFloat() * 0.3f).coerceIn(0f, 1f),
                blue = (baseColor.blue + Random.nextFloat() * 0.3f).coerceIn(0f, 1f)
            ),
            life = Random.nextFloat() * 2 + 1,
            maxLife = Random.nextFloat() * 2 + 1,
            rotation = Random.nextFloat() * 360,
            rotationSpeed = Random.nextFloat() * 10 - 5
        )
    }
}

七、高级动画特性

7.1 使用Compose动画API

Jetpack Compose提供了丰富的动画API,包括animate*AsStateupdateTransitionAnimatedVisibility等,可以轻松实现各种动画效果。

// 使用animateFloatAsState实现平滑过渡
val animatedAlpha by animateFloatAsState(
    targetValue = if (isVisible) 1f else 0f,
    animationSpec = tween(durationMillis = 300)
)

// 使用updateTransition管理多个动画状态
val transition = updateTransition(targetState = isVisible)
val alpha by transition.animateFloat { if (it) 1f else 0f }
val scale by transition.animateFloat { if (it) 1f else 0.5f }

7.2 物理动画效果

Compose提供Spring(弹簧)、Tween(缓动)、Keyframes(关键帧)等丰富插值器,替代传统Interpolator机制。

val animatedValue by animateDpAsState(
    targetValue = 100.dp,
    animationSpec = spring(
        dampingRatio = Spring.DampingRatioMediumBouncy,
        stiffness = Spring.StiffnessLow
    )
)

八、性能优化与调试

8.1 性能优化策略

GPU Rendering图表评估:启用”Profile GPU Rendering”功能,在设备开发者选项中选择”On screen as bars”,查看各阶段渲染时间条形图。理想状态下所有柱状图应低于绿色基线。

分层策略对比

  • ✅ 正确:子视图独立分层 – 最佳,适用于多个子视图独立动画
  • ⚠️ 错误:父容器分层 – 性能下降,子视图变化导致父层频繁失效
  • ⭐⭐ 混合分层 – 良好,适用于静态背景+动态前景组合

8.2 内存优化技巧

// 仅对需要动画的部分启用硬件层
val targetView = view.findViewById<View>(R.id.animating_part)
targetView.setLayerType(View.LAYER_TYPE_HARDWARE, null)

// 使用弱引用防止内存泄漏
val weakView = WeakReference(targetView)
ObjectAnimator.ofFloat(targetView, "translationX", 0f, 200f).apply {
    duration = 700
    addListener(object : AnimatorListenerAdapter() {
        override fun onAnimationEnd(animation: Animator) {
            weakView.get()?.setLayerType(View.LAYER_TYPE_NONE, null)
        }
    })
    start()
}

8.3 避免纹理失效

❌ 错误示例:动画中改变视图内容会导致纹理重绘

fun startProblematicAnimation(view: TextView) {
    view.setLayerType(View.LAYER_TYPE_HARDWARE, null)
    val animator = ObjectAnimator.ofFloat(view, "translationX", 0f, 300f).apply {
        duration = 1000
        addUpdateListener {
            // 动画中改变文本会导致纹理重绘!
            view.text = "Frame: ${it.animatedFraction}"
        }
        addListener(object : AnimatorListenerAdapter() {
            override fun onAnimationEnd(animation: Animator) {
                view.setLayerType(View.LAYER_TYPE_NONE, null)
            }
        })
    }
    animator.start()
}

✅ 正确做法:预先准备内容

fun startOptimizedTextAnimation(view: TextView) {
    val finalText = "Animation Completed"
    view.setLayerType(View.LAYER_TYPE_HARDWARE, null)
    ObjectAnimator.ofFloat(view, "translationX", 0f, 300f).apply {
        duration = 1000
        addListener(object : AnimatorListenerAdapter() {
            override fun onAnimationEnd(animation: Animator) {
                // 动画结束后更新内容
                view.text = finalText
                view.setLayerType(View.LAYER_TYPE_NONE, null)
            }
        })
        start()
    }
}

九、项目工程化落地

9.1 模块划分

采用分层架构提升可维护性,分离动画引擎、视图层、控制器。

9.2 可配置参数抽取

res/values/attrs.xml中定义可调参数:

<declare-styleable name="FireworkView">
    <attr name="particleCount" format="integer" />
    <attr name="gravityStrength" format="float" />
    <attr name="initialVelocity" format="float" />
</declare-styleable>

Java层解析:

val a = context.obtainStyledAttributes(attrs, R.styleable.FireworkView)
val count = a.getInt(R.styleable.FireworkView_particleCount, 100)
a.recycle()

9.3 发布前编译优化

启用混淆规则保留关键类:

-keep class com.example.firework.engine.Particle { *; }
-keep class com.example.firework.view.FireworkView { *; }
-dontwarn com.example.firework.**

并通过shrinkResources true移除未引用资源,最终APK大小可压缩30%以上。

十、总结

通过Jetpack Compose、协程、Flow和Canvas技术的结合,我们成功实现了一个高性能的烟花动画效果。核心要点包括:

  1. 粒子系统建模:定义粒子数据模型,包含位置、速度、颜色、生命周期等属性
  2. 协程控制:使用LaunchedEffect和Flow实现60fps的粒子更新
  3. 物理模拟:实现重力、空气阻力、旋转等物理效果
  4. Canvas渲染:使用BlendMode.Plus增强视觉效果
  5. 性能优化:通过协程调度、粒子过滤、批量渲染等技术提升性能

该方案在中端设备上可稳定支持200+粒子同时渲染,为Android应用增添了绚丽的视觉特效。

版权声明:本文为JienDa博主的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
若内容若侵犯到您的权益,请发送邮件至:platform_service@jienda.com我们将第一时间处理!
所有资源仅限于参考和学习,版权归JienDa作者所有,更多请访问JienDa首页。

给TA赞助
共{{data.count}}人
人已赞助
Android

Kotlin开发进阶:从基础到生产级应用的最佳实践指南

2025-12-23 22:16:13

Android

Flutter气泡弹窗库封装实战:从零到一打造精致UI组件

2025-12-23 22:25:07

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索