Flutter for Harmony 跨平台开发实战:声波干涉与驻波——叠加原理的波纹艺术
本篇文章深入探讨了声波干涉与驻波的数学原理及其在 Flutter 中的可视化实现。
·

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
🌊 一、声波干涉:数学之美
📚 1.1 波动基础
波是能量和信息传递的基本形式。声波作为机械波,通过介质粒子的振动传播能量。
波的基本方程:
一维波动方程:
∂²y/∂t² = v² · ∂²y/∂x²
其中:
- y:位移
- t:时间
- x:位置
- v:波速
简谐波解:
y(x, t) = A·sin(kx - ωt + φ)
参数说明:
- A:振幅
- k = 2π/λ:波数
- ω = 2πf:角频率
- φ:初相位
- λ:波长
- f:频率
- v = λf:波速
波形示意:
y(x, 0) = A·sin(kx)
A ────●─────────────────
╱ ╲
╱ ╲
0 ─●───────●───────●──── 平衡位置
╲ ╱
╲ ╱
-A ────●─────────────────
0 λ/4 λ/2 3λ/4 λ
波长 λ:相邻波峰(或波谷)之间的距离
波的传播:
t = 0: t = T/4: t = T/2: t = 3T/4: t = T:
● ● ● ● ●
╱ ╲ ╱ ╲ ╱ ╲ ╱ ╲ ╱ ╲
● ● ● ● ● ● ● ● ● ●
╲ ╱ ╲ ╱ ╲ ╱ ╲ ╱ ╲ ╱
● ● ● ● ●
→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→
波的传播方向
波形向右移动一个波长 λ 需要一个周期 T
📐 1.2 叠加原理
当两列或多列波在空间中相遇时,它们遵循叠加原理:合位移等于各波单独作用时位移的矢量和。
叠加原理:
y_total = y₁ + y₂ + y₃ + ...
对于两列简谐波:
y₁ = A₁·sin(k₁x - ω₁t + φ₁)
y₂ = A₂·sin(k₂x - ω₂t + φ₂)
合成波:
y = y₁ + y₂
特殊情况:
1. 同频率、同方向:
y = A·sin(kx - ωt + φ)
其中 A 和 φ 取决于相位差
2. 同频率、反方向(驻波):
y = 2A·cos(kx)·sin(ωt)
3. 频率相近(拍):
y = 2A·cos(Δω·t/2)·sin(ω_avg·t)
干涉类型:
| 类型 | 条件 | 结果 |
|---|---|---|
| 🟢 相长干涉 | 相位差 = 2nπ | 振幅增大 |
| 🔴 相消干涉 | 相位差 = (2n+1)π | 振幅减小 |
| 🟡 部分干涉 | 其他相位差 | 部分叠加 |
🔬 1.3 驻波形成
当两列振幅、频率相同但方向相反的波相遇时,形成驻波。驻波的特点是某些点始终静止(波节),某些点振动最大(波腹)。
驻波方程:
入射波:y₁ = A·sin(kx - ωt)
反射波:y₂ = A·sin(kx + ωt)
叠加:
y = y₁ + y₂ = 2A·sin(kx)·cos(ωt)
驻波特点:
- 波节:sin(kx) = 0 → x = nλ/2
- 波腹:sin(kx) = ±1 → x = (2n+1)λ/4
- 相邻波节距离:λ/2
- 相邻波腹距离:λ/2
驻波示意:
不同时刻的波形:
t = 0: t = T/4: t = T/2: t = 3T/4:
● ● ●
╱ ╲ ╱ ╲ ╱╲ ╱ ╲
● ● ● ● ● ● ● ●
╲ ╱ ╲ ╱ ╲╱ ╲ ╱
● ● ● ●
N A N A N A N A N
│ │ │ │ │ │ │ │ │
波节(N):始终静止
波腹(A):振动最大
相邻波节距离 = λ/2
驻波的应用:
1. 弦乐器:
- 两端固定的弦形成驻波
- 基频:f₁ = v/(2L)
- 泛音:fₙ = n·f₁
2. 管乐器:
- 开管:两端为波腹
- 闭管:一端波节,一端波腹
3. 微波炉:
- 腔体内形成驻波
- 加热不均匀的原因
4. 量子力学:
- 电子在原子中的驻波
- 波函数的驻波解
🎯 1.4 拍现象
当两个频率相近的波叠加时,产生振幅周期性变化的现象,称为拍。
拍的形成:
两个频率相近的波:
y₁ = A·sin(ω₁t)
y₂ = A·sin(ω₂t)
叠加:
y = 2A·cos(Δω·t/2)·sin(ω_avg·t)
其中:
- Δω = |ω₁ - ω₂|:角频率差
- ω_avg = (ω₁ + ω₂)/2:平均角频率
- 拍频:f_beat = |f₁ - f₂|
振幅包络:
A(t) = 2A·|cos(Δω·t/2)|
拍现象示意:
高频载波被低频包络调制:
╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
╱ ╲
╱ ╲
╱ ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
╱ ╲ ╲
╲ ╲
╲ ╲
╲╱╲╱╲╱╲╱╲╱╲╱╲╱
包络频率 = 拍频 = |f₁ - f₂|
🔧 二、波动模拟的 Dart 实现
🧮 2.1 波动方程求解
import 'dart:math';
/// 波动参数
class WaveParameters {
final double amplitude;
final double wavelength;
final double frequency;
final double phase;
final double speed;
WaveParameters({
required this.amplitude,
required this.wavelength,
required this.frequency,
this.phase = 0,
}) : speed = wavelength * frequency;
double get waveNumber => 2 * pi / wavelength;
double get angularFrequency => 2 * pi * frequency;
double get period => 1 / frequency;
double displacement(double x, double t) {
return amplitude * sin(waveNumber * x - angularFrequency * t + phase);
}
}
/// 波动模拟器
class WaveSimulator {
final List<WaveParameters> waves;
WaveSimulator({List<WaveParameters>? waves}) : waves = waves ?? [];
void addWave(WaveParameters wave) {
waves.add(wave);
}
double totalDisplacement(double x, double t) {
return waves.fold(0.0, (sum, wave) => sum + wave.displacement(x, t));
}
List<double> generateWaveform(double xStart, double xEnd, int points, double t) {
final dx = (xEnd - xStart) / (points - 1);
return List.generate(points, (i) {
final x = xStart + i * dx;
return totalDisplacement(x, t);
});
}
}
/// 驻波模拟器
class StandingWaveSimulator {
final double amplitude;
final double wavelength;
final double frequency;
StandingWaveSimulator({
required this.amplitude,
required this.wavelength,
required this.frequency,
});
double get waveNumber => 2 * pi / wavelength;
double get angularFrequency => 2 * pi * frequency;
double displacement(double x, double t) {
return 2 * amplitude * sin(waveNumber * x) * cos(angularFrequency * t);
}
List<double> nodePositions(double length) {
final positions = <double>[];
final n = (length * 2 / wavelength).floor();
for (int i = 0; i <= n; i++) {
positions.add(i * wavelength / 2);
}
return positions;
}
List<double> antinodePositions(double length) {
final positions = <double>[];
final n = (length * 2 / wavelength).floor();
for (int i = 0; i < n; i++) {
positions.add((i + 0.5) * wavelength / 2);
}
return positions;
}
}
/// 拍现象模拟器
class BeatSimulator {
final double amplitude;
final double frequency1;
final double frequency2;
BeatSimulator({
required this.amplitude,
required this.frequency1,
required this.frequency2,
});
double get beatFrequency => (frequency1 - frequency2).abs();
double get averageFrequency => (frequency1 + frequency2) / 2;
double displacement(double t) {
final omega1 = 2 * pi * frequency1;
final omega2 = 2 * pi * frequency2;
return 2 * amplitude * cos((omega1 - omega2) * t / 2) * sin((omega1 + omega2) * t / 2);
}
double envelope(double t) {
return 2 * amplitude * cos(pi * beatFrequency * t).abs();
}
}
⚡ 2.2 二维干涉模拟
/// 二维波动场
class WaveField2D {
final int width;
final int height;
final double dx;
late List<List<double>> _field;
late List<List<double>> _previousField;
final double waveSpeed;
final double damping;
WaveField2D({
required this.width,
required this.height,
this.dx = 1.0,
this.waveSpeed = 1.0,
this.damping = 0.99,
}) {
_field = List.generate(height, (_) => List.filled(width, 0.0));
_previousField = List.generate(height, (_) => List.filled(width, 0.0));
}
void addSource(int x, int y, double amplitude) {
if (x >= 0 && x < width && y >= 0 && y < height) {
_field[y][x] += amplitude;
}
}
void update(double dt) {
final c2 = waveSpeed * waveSpeed;
final r = c2 * dt * dt / (dx * dx);
final newField = List.generate(height, (_) => List.filled(width, 0.0));
for (int y = 1; y < height - 1; y++) {
for (int x = 1; x < width - 1; x++) {
final laplacian = _field[y][x - 1] + _field[y][x + 1] +
_field[y - 1][x] + _field[y + 1][x] -
4 * _field[y][x];
newField[y][x] = damping * (2 * _field[y][x] - _previousField[y][x] + r * laplacian);
}
}
_previousField = _field;
_field = newField;
}
double getValue(int x, int y) {
if (x >= 0 && x < width && y >= 0 && y < height) {
return _field[y][x];
}
return 0;
}
void reset() {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
_field[y][x] = 0;
_previousField[y][x] = 0;
}
}
}
}
/// 双缝干涉模拟
class DoubleSlitInterference {
final int gridSize;
final double slitSeparation;
final double slitWidth;
final double wavelength;
final double screenDistance;
DoubleSlitInterference({
required this.gridSize,
required this.slitSeparation,
required this.slitWidth,
required this.wavelength,
required this.screenDistance,
});
double intensity(double y) {
final k = 2 * pi / wavelength;
final theta = atan(y / screenDistance);
final alpha = k * slitSeparation * sin(theta) / 2;
final beta = k * slitWidth * sin(theta) / 2;
final interferencePattern = cos(alpha) * cos(alpha);
final diffractionEnvelope = sinc(beta) * sinc(beta);
return interferencePattern * diffractionEnvelope;
}
double sinc(double x) {
if (x.abs() < 1e-10) return 1.0;
return sin(x) / x;
}
List<double> generatePattern(double yMin, double yMax, int points) {
final dy = (yMax - yMin) / (points - 1);
return List.generate(points, (i) {
final y = yMin + i * dy;
return intensity(y);
});
}
}
🎨 2.3 动画控制器
import 'package:flutter/material.dart';
/// 波动动画控制器
class WaveAnimationController extends ChangeNotifier {
double _time = 0;
double _timeScale = 1.0;
bool _isRunning = true;
final WaveSimulator _simulator = WaveSimulator();
final List<double> _history = [];
final int _maxHistoryLength = 500;
WaveAnimationController() {
_simulator.addWave(WaveParameters(
amplitude: 1.0,
wavelength: 100.0,
frequency: 1.0,
phase: 0,
));
}
double get time => _time;
double get timeScale => _timeScale;
bool get isRunning => _isRunning;
WaveSimulator get simulator => _simulator;
List<double> get history => _history;
void update(double dt) {
if (_isRunning) {
_time += dt * _timeScale;
notifyListeners();
}
}
void toggleRunning() {
_isRunning = !_isRunning;
notifyListeners();
}
void setTimeScale(double scale) {
_timeScale = scale.clamp(0.1, 5.0);
notifyListeners();
}
void addWave(double amplitude, double wavelength, double frequency, double phase) {
_simulator.addWave(WaveParameters(
amplitude: amplitude,
wavelength: wavelength,
frequency: frequency,
phase: phase,
));
notifyListeners();
}
void reset() {
_time = 0;
_history.clear();
notifyListeners();
}
}
📦 三、完整示例代码
以下是完整的波动干涉可视化示例代码:
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(const WaveApp());
}
class WaveApp extends StatelessWidget {
const WaveApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: '声波干涉',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue, brightness: Brightness.dark),
useMaterial3: true,
),
home: const WaveHomePage(),
debugShowCheckedModeBanner: false,
);
}
}
class WaveHomePage extends StatelessWidget {
const WaveHomePage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('🌊 声波干涉'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
_buildCard(context, title: '波的叠加', description: '多波干涉原理', icon: Icons.add_circle, color: Colors.blue,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const WaveSuperpositionDemo()))),
_buildCard(context, title: '驻波形成', description: '波节与波腹', icon: Icons.horizontal_rule, color: Colors.purple,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const StandingWaveDemo()))),
_buildCard(context, title: '拍现象', description: '频率差产生的节拍', icon: Icons.graphic_eq, color: Colors.orange,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const BeatDemo()))),
_buildCard(context, title: '双缝干涉', description: '杨氏双缝实验', icon: Icons.view_column, color: Colors.teal,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const DoubleSlitDemo()))),
],
),
);
}
Widget _buildCard(BuildContext context, {required String title, required String description, required IconData icon, required Color color, required VoidCallback onTap}) {
return Card(
margin: const EdgeInsets.only(bottom: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(16),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(children: [
Container(width: 56, height: 56, decoration: BoxDecoration(color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(12)),
child: Icon(icon, color: color, size: 28)),
const SizedBox(width: 16),
Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
const SizedBox(height: 4),
Text(description, style: TextStyle(color: Colors.grey[600], fontSize: 14)),
])),
Icon(Icons.chevron_right, color: Colors.grey[400]),
]),
),
),
);
}
}
/// 波动参数
class Wave {
double amplitude, wavelength, frequency, phase;
Wave({required this.amplitude, required this.wavelength, required this.frequency, this.phase = 0});
double get waveNumber => 2 * pi / wavelength;
double get angularFrequency => 2 * pi * frequency;
double displacement(double x, double t) => amplitude * sin(waveNumber * x - angularFrequency * t + phase);
}
/// 波的叠加演示
class WaveSuperpositionDemo extends StatefulWidget {
const WaveSuperpositionDemo({super.key});
State<WaveSuperpositionDemo> createState() => _WaveSuperpositionDemoState();
}
class _WaveSuperpositionDemoState extends State<WaveSuperpositionDemo> with SingleTickerProviderStateMixin {
late AnimationController _ctrl;
double _time = 0;
final List<Wave> _waves = [Wave(amplitude: 1, wavelength: 100, frequency: 1), Wave(amplitude: 0.8, wavelength: 120, frequency: 0.8)];
double _phaseDiff = 0;
void initState() {
super.initState();
_ctrl = AnimationController(vsync: this, duration: const Duration(milliseconds: 16))..repeat();
_ctrl.addListener(_update);
}
void _update() { _time += 0.016; setState(() {}); }
void dispose() { _ctrl.removeListener(_update); _ctrl.dispose(); super.dispose(); }
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('波的叠加')),
body: Column(children: [
Expanded(child: CustomPaint(painter: SuperpositionPainter(_waves, _time), size: Size.infinite)),
_buildControls(),
]),
);
}
Widget _buildControls() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(color: Colors.grey[900], borderRadius: const BorderRadius.vertical(top: Radius.circular(20))),
child: Column(mainAxisSize: MainAxisSize.min, children: [
Row(children: [
const Text('相位差: ', style: TextStyle(color: Colors.white70)),
Expanded(child: Slider(value: _phaseDiff, min: 0, max: 2 * pi, onChanged: (v) => setState(() { _phaseDiff = v; _waves[1].phase = v; }))),
Text('${(_phaseDiff / pi).toStringAsFixed(2)}π', style: const TextStyle(color: Colors.blue)),
]),
Row(children: [
const Text('波2振幅: ', style: TextStyle(color: Colors.white70)),
Expanded(child: Slider(value: _waves[1].amplitude, min: 0, max: 2, onChanged: (v) => setState(() => _waves[1].amplitude = v))),
Text(_waves[1].amplitude.toStringAsFixed(2), style: const TextStyle(color: Colors.blue)),
]),
Text(_getInterferenceType(), style: TextStyle(color: _getInterferenceColor())),
]),
);
}
String _getInterferenceType() {
if (_phaseDiff < 0.1 || (_phaseDiff - 2 * pi).abs() < 0.1) return '相长干涉 - 振幅增大';
if ((_phaseDiff - pi).abs() < 0.1) return '相消干涉 - 振幅减小';
return '部分干涉';
}
Color _getInterferenceColor() {
if (_phaseDiff < 0.1 || (_phaseDiff - 2 * pi).abs() < 0.1) return Colors.green;
if ((_phaseDiff - pi).abs() < 0.1) return Colors.red;
return Colors.yellow;
}
}
class SuperpositionPainter extends CustomPainter {
final List<Wave> waves;
final double time;
SuperpositionPainter(this.waves, this.time);
void paint(Canvas canvas, Size size) {
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
final colors = [Colors.blue, Colors.orange, Colors.green];
final centerY = size.height / 2;
final scale = size.height / 4;
for (int w = 0; w < waves.length; w++) {
final path = Path();
for (int i = 0; i <= size.width.toInt(); i++) {
final x = i.toDouble();
final y = waves[w].displacement(x, time) * scale;
if (i == 0) path.moveTo(x, centerY - y);
else path.lineTo(x, centerY - y);
}
canvas.drawPath(path, Paint()..color = colors[w].withOpacity(0.5)..style = PaintingStyle.stroke..strokeWidth = 2);
}
final sumPath = Path();
for (int i = 0; i <= size.width.toInt(); i++) {
final x = i.toDouble();
double sum = 0;
for (final wave in waves) { sum += wave.displacement(x, time); }
final y = sum * scale;
if (i == 0) sumPath.moveTo(x, centerY - y);
else sumPath.lineTo(x, centerY - y);
}
canvas.drawPath(sumPath, Paint()..color = Colors.white..style = PaintingStyle.stroke..strokeWidth = 3);
}
bool shouldRepaint(covariant SuperpositionPainter old) => true;
}
/// 驻波演示
class StandingWaveDemo extends StatefulWidget {
const StandingWaveDemo({super.key});
State<StandingWaveDemo> createState() => _StandingWaveDemoState();
}
class _StandingWaveDemoState extends State<StandingWaveDemo> with SingleTickerProviderStateMixin {
late AnimationController _ctrl;
double _time = 0;
double _wavelength = 100;
double _frequency = 1;
double _amplitude = 1;
void initState() {
super.initState();
_ctrl = AnimationController(vsync: this, duration: const Duration(milliseconds: 16))..repeat();
_ctrl.addListener(_update);
}
void _update() { _time += 0.016; setState(() {}); }
void dispose() { _ctrl.removeListener(_update); _ctrl.dispose(); super.dispose(); }
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('驻波形成')),
body: Column(children: [
Expanded(child: CustomPaint(painter: StandingWavePainter(_wavelength, _frequency, _amplitude, _time), size: Size.infinite)),
_buildControls(),
]),
);
}
Widget _buildControls() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(color: Colors.grey[900], borderRadius: const BorderRadius.vertical(top: Radius.circular(20))),
child: Column(mainAxisSize: MainAxisSize.min, children: [
Row(children: [
const Text('波长: ', style: TextStyle(color: Colors.white70)),
Expanded(child: Slider(value: _wavelength, min: 50, max: 200, onChanged: (v) => setState(() => _wavelength = v))),
Text(_wavelength.toStringAsFixed(0), style: const TextStyle(color: Colors.purple)),
]),
Row(children: [
const Text('频率: ', style: TextStyle(color: Colors.white70)),
Expanded(child: Slider(value: _frequency, min: 0.5, max: 3, onChanged: (v) => setState(() => _frequency = v))),
Text(_frequency.toStringAsFixed(2), style: const TextStyle(color: Colors.purple)),
]),
Text('波节间距: ${(_wavelength / 2).toStringAsFixed(1)}', style: const TextStyle(color: Colors.white70)),
]),
);
}
}
class StandingWavePainter extends CustomPainter {
final double wavelength, frequency, amplitude, time;
StandingWavePainter(this.wavelength, this.frequency, this.amplitude, this.time);
void paint(Canvas canvas, Size size) {
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
final k = 2 * pi / wavelength;
final omega = 2 * pi * frequency;
final centerY = size.height / 2;
final scale = size.height / 4;
for (int i = 0; i <= size.width.toInt(); i++) {
final x = i.toDouble();
final nodeVal = sin(k * x).abs();
final hue = nodeVal * 60;
canvas.drawCircle(Offset(x, centerY), 1, Paint()..color = HSVColor.fromAHSV(0.3, hue, 0.8, 0.8).toColor());
}
final path = Path();
for (int i = 0; i <= size.width.toInt(); i++) {
final x = i.toDouble();
final y = 2 * amplitude * sin(k * x) * cos(omega * time) * scale;
if (i == 0) path.moveTo(x, centerY - y);
else path.lineTo(x, centerY - y);
}
canvas.drawPath(path, Paint()..color = Colors.purple..style = PaintingStyle.stroke..strokeWidth = 3);
for (double nodeX = 0; nodeX <= size.width; nodeX += wavelength / 2) {
canvas.drawCircle(Offset(nodeX, centerY), 6, Paint()..color = Colors.red);
}
for (double antinodeX = wavelength / 4; antinodeX <= size.width; antinodeX += wavelength / 2) {
canvas.drawCircle(Offset(antinodeX, centerY), 6, Paint()..color = Colors.green);
}
}
bool shouldRepaint(covariant StandingWavePainter old) => true;
}
/// 拍现象演示
class BeatDemo extends StatefulWidget {
const BeatDemo({super.key});
State<BeatDemo> createState() => _BeatDemoState();
}
class _BeatDemoState extends State<BeatDemo> with SingleTickerProviderStateMixin {
late AnimationController _ctrl;
double _time = 0;
double _freq1 = 5.0;
double _freq2 = 5.5;
void initState() {
super.initState();
_ctrl = AnimationController(vsync: this, duration: const Duration(milliseconds: 16))..repeat();
_ctrl.addListener(_update);
}
void _update() { _time += 0.016; setState(() {}); }
void dispose() { _ctrl.removeListener(_update); _ctrl.dispose(); super.dispose(); }
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('拍现象')),
body: Column(children: [
Expanded(child: CustomPaint(painter: BeatPainter(_freq1, _freq2, _time), size: Size.infinite)),
_buildControls(),
]),
);
}
Widget _buildControls() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(color: Colors.grey[900], borderRadius: const BorderRadius.vertical(top: Radius.circular(20))),
child: Column(mainAxisSize: MainAxisSize.min, children: [
Row(children: [
const Text('频率1: ', style: TextStyle(color: Colors.white70)),
Expanded(child: Slider(value: _freq1, min: 1, max: 10, onChanged: (v) => setState(() => _freq1 = v))),
Text('${_freq1.toStringAsFixed(1)} Hz', style: const TextStyle(color: Colors.orange)),
]),
Row(children: [
const Text('频率2: ', style: TextStyle(color: Colors.white70)),
Expanded(child: Slider(value: _freq2, min: 1, max: 10, onChanged: (v) => setState(() => _freq2 = v))),
Text('${_freq2.toStringAsFixed(1)} Hz', style: const TextStyle(color: Colors.orange)),
]),
Text('拍频: ${(_freq1 - _freq2).abs().toStringAsFixed(2)} Hz', style: const TextStyle(color: Colors.white70)),
]),
);
}
}
class BeatPainter extends CustomPainter {
final double freq1, freq2, time;
BeatPainter(this.freq1, this.freq2, this.time);
void paint(Canvas canvas, Size size) {
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
final omega1 = 2 * pi * freq1;
final omega2 = 2 * pi * freq2;
final beatFreq = (freq1 - freq2).abs();
final avgFreq = (freq1 + freq2) / 2;
final centerY = size.height / 2;
final scale = size.height / 4;
final envelopePath = Path();
for (int i = 0; i <= size.width.toInt(); i++) {
final t = time + i * 0.001;
final env = 2 * cos(pi * beatFreq * t).abs() * scale;
if (i == 0) envelopePath.moveTo(i.toDouble(), centerY - env);
else envelopePath.lineTo(i.toDouble(), centerY - env);
}
for (int i = size.width.toInt(); i >= 0; i--) {
final t = time + i * 0.001;
final env = 2 * cos(pi * beatFreq * t).abs() * scale;
envelopePath.lineTo(i.toDouble(), centerY + env);
}
envelopePath.close();
canvas.drawPath(envelopePath, Paint()..color = Colors.orange.withOpacity(0.2));
final wavePath = Path();
for (int i = 0; i <= size.width.toInt(); i++) {
final t = time + i * 0.001;
final y = 2 * cos((omega1 - omega2) * t / 2) * sin((omega1 + omega2) * t / 2) * scale;
if (i == 0) wavePath.moveTo(i.toDouble(), centerY - y);
else wavePath.lineTo(i.toDouble(), centerY - y);
}
canvas.drawPath(wavePath, Paint()..color = Colors.orange..style = PaintingStyle.stroke..strokeWidth = 2);
}
bool shouldRepaint(covariant BeatPainter old) => true;
}
/// 双缝干涉演示
class DoubleSlitDemo extends StatefulWidget {
const DoubleSlitDemo({super.key});
State<DoubleSlitDemo> createState() => _DoubleSlitDemoState();
}
class _DoubleSlitDemoState extends State<DoubleSlitDemo> {
double _slitSeparation = 50;
double _wavelength = 20;
double _screenDistance = 200;
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('双缝干涉')),
body: Column(children: [
Expanded(child: CustomPaint(painter: DoubleSlitPainter(_slitSeparation, _wavelength, _screenDistance), size: Size.infinite)),
_buildControls(),
]),
);
}
Widget _buildControls() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(color: Colors.grey[900], borderRadius: const BorderRadius.vertical(top: Radius.circular(20))),
child: Column(mainAxisSize: MainAxisSize.min, children: [
Row(children: [
const Text('缝间距: ', style: TextStyle(color: Colors.white70)),
Expanded(child: Slider(value: _slitSeparation, min: 20, max: 100, onChanged: (v) => setState(() => _slitSeparation = v))),
Text(_slitSeparation.toStringAsFixed(0), style: const TextStyle(color: Colors.teal)),
]),
Row(children: [
const Text('波长: ', style: TextStyle(color: Colors.white70)),
Expanded(child: Slider(value: _wavelength, min: 10, max: 50, onChanged: (v) => setState(() => _wavelength = v))),
Text(_wavelength.toStringAsFixed(0), style: const TextStyle(color: Colors.teal)),
]),
Text('条纹间距: ${(_wavelength * _screenDistance / _slitSeparation).toStringAsFixed(1)}', style: const TextStyle(color: Colors.white70)),
]),
);
}
}
class DoubleSlitPainter extends CustomPainter {
final double slitSeparation, wavelength, screenDistance;
DoubleSlitPainter(this.slitSeparation, this.wavelength, this.screenDistance);
void paint(Canvas canvas, Size size) {
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
final centerX = size.width * 0.2;
final centerY = size.height / 2;
final screenX = size.width * 0.9;
canvas.drawRect(Rect.fromLTWH(centerX - 5, 0, 10, centerY - slitSeparation / 2 - 5), Paint()..color = Colors.grey);
canvas.drawRect(Rect.fromLTWH(centerX - 5, centerY - slitSeparation / 2 + 5, 10, slitSeparation - 10), Paint()..color = Colors.grey);
canvas.drawRect(Rect.fromLTWH(centerX - 5, centerY + slitSeparation / 2 + 5, 10, size.height - centerY - slitSeparation / 2 - 5), Paint()..color = Colors.grey);
for (int i = 0; i < 8; i++) {
final angle = (i - 4) * 0.1;
final endY = centerY + tan(angle) * (screenX - centerX);
final path = Path();
path.moveTo(centerX, centerY - slitSeparation / 2);
path.lineTo(screenX, endY);
canvas.drawPath(path, Paint()..color = Colors.teal.withOpacity(0.3)..style = PaintingStyle.stroke);
final path2 = Path();
path2.moveTo(centerX, centerY + slitSeparation / 2);
path2.lineTo(screenX, endY);
canvas.drawPath(path2, Paint()..color = Colors.teal.withOpacity(0.3)..style = PaintingStyle.stroke);
}
final k = 2 * pi / wavelength;
for (int y = 0; y < size.height.toInt(); y++) {
final dy = y - centerY;
final pathDiff = sqrt(pow(screenX - centerX, 2) + pow(dy - slitSeparation / 2, 2)) -
sqrt(pow(screenX - centerX, 2) + pow(dy + slitSeparation / 2, 2));
final intensity = cos(k * pathDiff / 2) * cos(k * pathDiff / 2);
final hue = 180 + intensity * 60;
canvas.drawLine(Offset(screenX - 30, y.toDouble()), Offset(screenX, y.toDouble()),
Paint()..color = HSVColor.fromAHSV(1, hue, 0.8, intensity).toColor()..strokeWidth = 1);
}
}
bool shouldRepaint(covariant DoubleSlitPainter old) => false;
}
📝 四、数学原理深入解析
📐 4.1 波动方程推导
从牛顿定律到波动方程:
考虑弦上的微小元素:
张力 T 沿弦方向
弦的线密度 μ
对于小位移 y,张力近似恒定
力的平衡:
T·sin(θ + Δθ) - T·sin(θ) = μ·Δx·∂²y/∂t²
小角度近似:sin(θ) ≈ tan(θ) = ∂y/∂x
T·(∂y/∂x|_{x+Δx} - ∂y/∂x|_x) = μ·Δx·∂²y/∂t²
两边除以 Δx,取极限:
T·∂²y/∂x² = μ·∂²y/∂t²
即:∂²y/∂t² = (T/μ)·∂²y/∂x²
波速:v = √(T/μ)
🔄 4.2 干涉条纹计算
双缝干涉光程差:
设两缝间距为 d,屏幕距离为 D
对于屏幕上 y 处的点:
- 到上缝距离:r₁ = √(D² + (y - d/2)²)
- 到下缝距离:r₂ = √(D² + (y + d/2)²)
光程差:Δr = r₂ - r₁
当 D >> d, y 时:
Δr ≈ d·sin(θ) ≈ d·y/D
明条纹条件:Δr = nλ
y_n = nλD/d
暗条纹条件:Δr = (n + 1/2)λ
y_n = (n + 1/2)λD/d
条纹间距:Δy = λD/d
🌸 4.3 驻波能量分布
驻波的能量分析:
驻波方程:y = 2A·sin(kx)·cos(ωt)
动能密度:
dk = ½ρ(∂y/∂t)² = 2ρA²ω²·sin²(kx)·sin²(ωt)
势能密度:
dp = ½T(∂y/∂x)² = 2TA²k²·cos²(kx)·cos²(ωt)
总能量密度:
dE = dk + dp
在波节处 (sin(kx) = 0):
- 动能密度 = 0
- 势能密度最大
在波腹处 (cos(kx) = 0):
- 动能密度最大
- 势能密度 = 0
能量在波节和波腹之间周期性转移
🎯 4.4 傅里叶分析
波的频谱分解:
任何周期波都可以分解为简谐波的叠加:
y(t) = Σ Aₙ·cos(nω₀t + φₙ)
傅里叶系数:
Aₙ = (2/T)∫₀ᵀ y(t)·cos(nω₀t)dt
φₙ = arctan(...)
对于方波:
y(t) = (4A/π)·[sin(ω₀t) + sin(3ω₀t)/3 + sin(5ω₀t)/5 + ...]
只有奇次谐波,振幅按 1/n 衰减
🔬 五、高级应用场景
🎨 5.1 音频可视化
频谱分析器:
class SpectrumAnalyzer {
final int sampleRate;
final int fftSize;
SpectrumAnalyzer({required this.sampleRate, this.fftSize = 1024});
List<double> analyze(List<double> samples) {
final spectrum = List<double>.filled(fftSize ~/ 2, 0);
for (int k = 0; k < fftSize ~/ 2; k++) {
double real = 0, imag = 0;
for (int n = 0; n < fftSize; n++) {
final angle = 2 * pi * k * n / fftSize;
real += samples[n] * cos(angle);
imag -= samples[n] * sin(angle);
}
spectrum[k] = sqrt(real * real + imag * imag) / fftSize;
}
return spectrum;
}
}
🌐 5.2 声学仿真
房间声学模拟:
class RoomAcoustics {
final double length, width, height;
final double absorption;
RoomAcoustics({required this.length, required this.width, required this.height, this.absorption = 0.3});
List<double> calculateModes(int maxOrder) {
final modes = <double>[];
final c = 343.0;
for (int nx = 0; nx <= maxOrder; nx++) {
for (int ny = 0; ny <= maxOrder; ny++) {
for (int nz = 0; nz <= maxOrder; nz++) {
if (nx + ny + nz > 0) {
final freq = c / 2 * sqrt(pow(nx / length, 2) + pow(ny / width, 2) + pow(nz / height, 2));
modes.add(freq);
}
}
}
}
modes.sort();
return modes;
}
double reverberationTime() {
final volume = length * width * height;
final surfaceArea = 2 * (length * width + length * height + width * height);
return 0.161 * volume / (surfaceArea * absorption);
}
}
📱 5.3 鸿蒙多端适配
性能优化配置:
class WaveSimulationConfig {
static int getOptimalGridSize(BuildContext context) {
final size = MediaQuery.of(context).size;
final minDim = min(size.width, size.height);
return (minDim / 2).toInt();
}
static double getOptimalTimeStep() {
return 0.016;
}
}
📊 六、性能优化策略
⚡ 6.1 FFT 优化
快速傅里叶变换:
class FFT {
static List<Complex> fft(List<Complex> x) {
final n = x.length;
if (n <= 1) return x;
final even = <Complex>[];
final odd = <Complex>[];
for (int i = 0; i < n; i += 2) {
even.add(x[i]);
if (i + 1 < n) odd.add(x[i + 1]);
}
final evenFFT = fft(even);
final oddFFT = fft(odd);
final result = List<Complex>.filled(n, Complex(0, 0));
for (int k = 0; k < n ~/ 2; k++) {
final t = Complex.fromPolar(1, -2 * pi * k / n) * oddFFT[k];
result[k] = evenFFT[k] + t;
result[k + n ~/ 2] = evenFFT[k] - t;
}
return result;
}
}
class Complex {
final double real, imag;
Complex(this.real, this.imag);
factory Complex.fromPolar(double r, double theta) => Complex(r * cos(theta), r * sin(theta));
Complex operator +(Complex other) => Complex(real + other.real, imag + other.imag);
Complex operator *(Complex other) => Complex(real * other.real - imag * other.imag, real * other.imag + imag * other.real);
}
💾 6.2 波形缓存
预计算优化:
class WaveformCache {
static final Map<String, List<double>> _cache = {};
static List<double> getWaveform(String key, double wavelength, double amplitude, int points) {
return _cache.putIfAbsent(key, () {
return List.generate(points, (i) {
final x = i * wavelength / points;
return amplitude * sin(2 * pi * x / wavelength);
});
});
}
}
🎓 七、学习资源与拓展
📚 推荐阅读
| 主题 | 资源 | 难度 |
|---|---|---|
| 波动理论 | 《物理学》- 哈里德 | ⭐⭐ |
| 声学 | 《声学基础》 | ⭐⭐⭐ |
| 光学 | 《光学原理》- 玻恩 | ⭐⭐⭐ |
| 信号处理 | 《数字信号处理》 | ⭐⭐⭐ |
🔗 相关项目
- Web Audio API:浏览器音频处理
- PortAudio:跨平台音频库
- JUCE:音频应用框架
📝 八、总结
本篇文章深入探讨了声波干涉与驻波的数学原理及其在 Flutter 中的可视化实现。
✅ 核心知识点回顾
| 知识点 | 说明 |
|---|---|
| 🌊 波动方程 | 波的传播与叠加 |
| 🔀 叠加原理 | 多波干涉现象 |
| ➖ 驻波形成 | 波节与波腹分布 |
| 🎵 拍现象 | 频率差产生的调制 |
| 🔬 双缝干涉 | 杨氏实验原理 |
⭐ 最佳实践要点
- ✅ 使用波动方程精确模拟
- ✅ 合理设置波长和频率参数
- ✅ 注意边界条件处理
- ✅ 使用 FFT 加速频谱分析
🚀 进阶方向
- 🔮 三维波动模拟
- ✨ 实时音频处理
- 📊 声场可视化
- 🎵 音频效果器开发
💡 提示:本文代码基于 Flutter for Harmony 开发,可在鸿蒙设备上流畅运行。
更多推荐



所有评论(0)