Jetpack Compose可自定义音频波纹动画(WaveAnimationBar)实现代码(Android and iOS)

136 阅读1分钟
package com.****.***.ui.components

import androidx.compose.animation.core.*
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import kotlin.math.*

@Composable
fun WaveAnimationBar(
    barCount: Int = 35,
    barWidth: Float = 7f,
    barMaxHeight: Float = 52f,
    barMinHeight: Float = 10f,
    barSpacing: Float = 7f,
    peakCount: Int = 10,
    color: Color = Color(0xff6700),
    isPlaying: Boolean = true, // 用于外部控制动画启动和停止
    modifier: Modifier = Modifier
) {
    // 1. phase动画:可控
    val phase = remember { Animatable(0f) }

    // 2. 启动/停止动画逻辑
    LaunchedEffect(isPlaying) {
        if (isPlaying) {
            while (true) {
                // 一个周期2PI
                phase.animateTo(
                    targetValue = phase.value + 2 * PI.toFloat(),
                    animationSpec = tween(
                        durationMillis = 1800,
                        easing = LinearEasing
                    )
                )
                // 懒得无限累加大, 清零下
                phase.snapTo(phase.value % (2 * PI.toFloat()))
            }
        }
    }

    Canvas(
        modifier = modifier
    ) {
        val totalWidth = barCount * barWidth + (barCount - 1) * barSpacing
        val startX = (size.width - totalWidth) / 2f
        for (i in 0 until barCount) {
            val ratio = i.toFloat() / (barCount - 1)

            // 包络线:中心最大,两侧递减
            val envelope = 0.4f + 0.6f * cos((ratio - 0.5f) * PI).toFloat()
            val barSin = (sin(peakCount * PI * ratio + phase.value) + 1f) / 2f
            val barHeight = barMinHeight + (barMaxHeight - barMinHeight) * barSin * envelope

            val x = startX + i * (barWidth + barSpacing)
            drawRoundRect(
              color = color,
              topLeft = androidx.compose.ui.geometry.Offset(x, ((size.height - barHeight) / 2f).toFloat()),
              size = androidx.compose.ui.geometry.Size(barWidth.toFloat(), barHeight.toFloat()),
              cornerRadius = androidx.compose.ui.geometry.CornerRadius(barWidth / 2f, barWidth / 2f)
            )
        }
    }
}

播放动效.gif