import 'dart:math'; import 'package:flutter/material.dart'; class VoiceWaveAnimation extends StatefulWidget { const VoiceWaveAnimation({super.key}); @override State createState() => _VoiceWaveAnimationState(); } class _VoiceWaveAnimationState extends State with TickerProviderStateMixin { late AnimationController _controller; @override void initState() { super.initState(); // 创建一个持续动画控制器 _controller = AnimationController( duration: const Duration(milliseconds: 1500), vsync: this, )..repeat(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _controller, builder: (context, child) { return SizedBox( width: 160, height: 30, child: CustomPaint( painter: SineWavePainter( animation: _controller, count: 2, // 波浪数量 color: Colors.white, amplitudeFactor: 0.5, // 波幅因子 ), ), ); }, ); } } class SineWavePainter extends CustomPainter { final Animation animation; final int count; // 波浪数量 final Color color; final double amplitudeFactor; // 波幅因子 SineWavePainter({ required this.animation, this.count = 2, required this.color, this.amplitudeFactor = 1.0, }); @override void paint(Canvas canvas, Size size) { final Paint paint = Paint() ..color = color.withValues(alpha: 0.7) ..style = PaintingStyle.stroke ..strokeWidth = 2.0; final Paint paint2 = Paint() ..color = color.withValues(alpha: 0.3) ..style = PaintingStyle.stroke ..strokeWidth = 2.0; final double width = size.width; final double height = size.height; final double centerY = height / 2; // 绘制第一个波浪 final path = Path(); final path2 = Path(); // 创建两条不同相位的波形,一条主要一条次要 path.moveTo(0, centerY); path2.moveTo(0, centerY); for (int i = 0; i < width; i++) { // 主波形 final double x = i.toDouble(); final double normalizedX = (x / width) * count * 2 * pi; final double offset = 2 * pi * animation.value; // 使用随机振幅变化模拟声音波动 final double randomAmplitude = (sin(normalizedX + offset) + 1) / 2 * amplitudeFactor; path.lineTo(x, centerY - randomAmplitude * 10); // 次波形,相位滞后 final double randomAmplitude2 = (sin(normalizedX + offset + pi / 6) + 1) / 2 * amplitudeFactor; path2.lineTo(x, centerY - randomAmplitude2 * 5); } canvas.drawPath(path, paint); canvas.drawPath(path2, paint2); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return true; } }