硬核春节:用 Compose 打造“赛博鞭炮”

马上就到春节了,最近也在学习Compose的动画,结合触觉反馈写了一个"赛博鞭炮"的动画效果,页面很简单,就一个鞭炮图片,点击后,回添加一个缩放动画,同时添加震动反馈,连续点击 20 次后会出现一个粒子爆炸的效果。


第一部分:技术拆解

在开始写代码之前,我们先准备好三样"火药":

1. 声明式动画 (Declarative Animation)

  • 技术点animateFloatAsState
  • 原理 :在 Compose 中,我们不再命令 View "开始播放动画"。相反,我们声明一个目标状态 (Target State)。比如,"现在的缩放比例是 1.5"。Compose 的动画系统会自动计算从"当前值"到"目标值"的补间数据,并逐帧重绘。
  • 适用场景:按钮点击缩放、颜色渐变、位置移动等简单的过渡效果。

2. 帧级物理渲染 (Frame-Loop Rendering)

  • 技术点Canvas + withFrameNanos
  • 原理:对于包含成百上千个粒子的爆炸效果,使用普通的 Composable 组件(如 Box)会带来巨大的性能开销(因为每个粒子都是一个 Layout Node)。
  • 解决方案 :我们使用 Canvas 直接在 GPU 上绘制,并配合 withFrameNanos 挂钩屏幕刷新率(通常是 60Hz 或 120Hz)。每一帧我们都手动计算粒子的物理位置(重力、速度),实现极致流畅的粒子系统。

3. 沉浸式触觉反馈 (Haptics)

  • 技术点VibratorManager & VibrationEffect
  • 原理:震动不再是单一的"嗡嗡"声。Android 提供了丰富的震动原语(Primitives)。
    • OneShot:单次短震,模拟点击。
    • Waveform:波形震动,可以定义一连串强弱变化的震动序列,模拟爆炸的冲击波。

第二部分:技术解析

1. 物理粒子系统模型

要实现真实的爆炸,不能只靠随机乱飞。我们需要简单的物理公式:

  • 水平运动 (匀速):
  • 垂直运动 (加速下落):
  • 衰减 (Fade Out): 透明度随时间线性降低。

在 Compose 中,我们在 CanvasonDraw 阶段应用这些公式:

Kotlin 复制代码
// 伪代码示例
particles.forEach { p ->
    // 计算当前位置
    val x = originX + p.vx * time
    val y = originY + p.vy * time + 0.5 * gravity * time * time
    
    // 绘制
    drawCircle(center = Offset(x, y), alpha = calculateAlpha(time))
}

2. 震动兼容性处理

Android 的震动 API 经历了多次迭代。为了兼容性,我们需要根据 SDK 版本分流:

  • Android 12 (S) 及以上 :使用 VibratorManager 获取默认震动器。
  • Android 12 以下 :直接使用 Vibrator 服务。

第三部分:综合实战

接下来,我们将上述技术熔炼在一起,实现完整的赛博鞭炮功能。

1. 定义数据结构

首先,定义粒子模型。

Kotlin 复制代码
data class ExplosionParticle(
    val vx: Float, // 水平速度向量
    val vy: Float, // 垂直速度向量
    val color: Color // 粒子颜色
)

2. 实现粒子爆炸层 (ExplosionOverlay)

这是技术含量最高的部分,使用了 CanvasFrameLoop

Kotlin 复制代码
@Composable
fun ExplosionOverlay(triggerId: Long) {
    // 1. 初始化粒子池:生成 100 个具有随机速度和颜色的粒子
    val particles = remember(triggerId) {
        List(100) {
            ExplosionParticle(
                vx = (Math.random() - 0.5).toFloat() * 0.05f,
                vy = (Math.random() - 0.5).toFloat() * 0.05f,
                color = listOf(Color.Yellow, Color.Red, Color.White).random()
            )
        }
    }

    var timeNanos by remember(triggerId) { mutableStateOf(0L) }

    // 2. 启动动画循环:每一帧更新时间
    LaunchedEffect(triggerId) {
        val startTime = withFrameNanos { it }
        while (true) {
            val playTime = withFrameNanos { it } - startTime
            timeNanos = playTime
            // 2秒后自动停止,释放资源
            if (playTime > 2_000_000_000L) break
        }
    }

    // 3. 绘制层:应用物理公式
    Canvas(modifier = Modifier.fillMaxSize()) {
        val timeFactor = timeNanos / 1_000_000_000f * 60f // 时间标准化
        val gravity = 0.0005f // 重力系数

        particles.forEach { p ->
            // x = x0 + vt
            val x = size.width / 2 + p.vx * timeFactor * size.width
            // y = y0 + vt + 0.5gt^2
            val y = size.height / 2 + (p.vy * timeFactor + 0.5f * gravity * timeFactor * timeFactor) * size.height
            
            // 计算透明度
            val alpha = (1f - timeFactor / 100f).coerceIn(0f, 1f)
            
            if (alpha > 0) {
                drawCircle(color = p.color.copy(alpha = alpha), radius = 10f * alpha, center = Offset(x, y))
            }
        }
    }
}

3. 主界面交互逻辑 (CyberFirecrackerScreen)

整合 动画状态震动业务逻辑

Kotlin 复制代码
@Composable
fun CyberFirecrackerScreen() {
    val context = LocalContext.current
    
    // 1. 准备震动器 (兼容性处理)
    val vibrator = remember {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            (context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager).defaultVibrator
        } else {
            context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
        }
    }

    // 2. 状态管理
    var scale by remember { mutableStateOf(1f) }
    val animatedScale by animateFloatAsState(targetValue = scale, label = "scale")
    var clickCount by remember { mutableStateOf(0) }
    var explosionTrigger by remember { mutableStateOf(0L) }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .clickable(
                interactionSource = remember { MutableInteractionSource() },
                indication = null // 去除默认波纹
            ) {
                // --- 交互逻辑开始 ---
                clickCount++
                
                // 3. 触发逻辑:满 20 次爆炸
                if (clickCount >= 20) {
                    clickCount = 0
                    explosionTrigger = System.currentTimeMillis() // 触发粒子动画
                    
                    // 播放爆炸震动 (Waveform)
                    val explosionWave = longArrayOf(0, 50, 50, 100, 50, 200)
                    vibrator.vibrate(VibrationEffect.createWaveform(explosionWave, -1))
                } else {
                    // 播放普通震动 (OneShot)
                    vibrator.vibrate(VibrationEffect.createOneShot(50, VibrationEffect.DEFAULT_AMPLITUDE))
                }

                // 4. 触发缩放动画
                scale = 1.2f // 变大
                // 使用协程延时恢复
                // 注意:在真实代码中需配合 LaunchedEffect 或 suspend function
            }
    ) {
        // 鞭炮主体
        Text("🧨", modifier = Modifier.scale(animatedScale).align(Alignment.Center))
        
        // 粒子层 (覆盖在最上层)
        if (explosionTrigger > 0L) {
            ExplosionOverlay(triggerId = explosionTrigger)
        }
    }
}

结语

Jetpack Compose 不仅仅是一个 UI 框架,它对 Canvas 绘图动画流 的支持让开发者能够轻松实现高性能的游戏化交互。

  • 声明式 UI 让状态管理变得清晰。
  • Canvas API 让物理模拟成为可能。
  • Vibrator API 补全了触觉体验的最后一块拼图。

现在,你已经拥有了一个赛博味十足的电子鞭炮,既环保又解压!

相关推荐
消失的旧时光-194310 小时前
从 Kotlin 到 Dart:为什么 sealed 是处理「多种返回结果」的最佳方式?
android·开发语言·flutter·架构·kotlin·sealed
有位神秘人10 小时前
kotlin与Java中的单例模式总结
java·单例模式·kotlin
Jinkxs10 小时前
Gradle - 与Groovy/Kotlin DSL对比 构建脚本语言选择指南
android·开发语言·kotlin
&有梦想的咸鱼&10 小时前
Kotlin委托机制的底层实现深度解析(74)
android·开发语言·kotlin
golang学习记10 小时前
IntelliJ IDEA 2025.3 重磅发布:K2 模式全面接管 Kotlin —— 告别 K1,性能飙升 40%!
java·kotlin·intellij-idea
LDORntKQH10 小时前
基于深度强化学习的混合动力汽车能量管理策略 1.利用DQN算法控制电池和发动机发电机组的功率分配 2
android
冬奇Lab10 小时前
Android 15 ServiceManager与Binder服务注册深度解析
android·源码·源码阅读
2501_9160088912 小时前
深入解析iOS机审4.3原理与混淆实战方法
android·java·开发语言·ios·小程序·uni-app·iphone
独行soc14 小时前
2026年渗透测试面试题总结-20(题目+回答)
android·网络·安全·web安全·渗透测试·安全狮