Flutter for Harmony 跨平台开发实战:弹簧振子系统——阻尼振动的视觉节拍
·

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
🎯 一、弹簧振子系统:数学之美
📚 1.1 简谐振动基础
简谐振动是最基本的振动形式,描述了物体在平衡位置附近的周期性运动。弹簧振子是简谐振动的经典模型。
胡克定律:
弹簧的恢复力与位移成正比:
F = -kx
其中:
- F:恢复力
- k:弹簧劲度系数
- x:位移
- 负号:力与位移方向相反
运动方程:
根据牛顿第二定律:F = ma
m(d²x/dt²) = -kx
整理得:
d²x/dt² + (k/m)x = 0
设 ω² = k/m,则:
d²x/dt² + ω²x = 0
这是简谐振动的微分方程
通解:
x(t) = A·cos(ωt + φ)
其中:
- A:振幅(最大位移)
- ω:角频率 = √(k/m)
- φ:初相位
- T:周期 = 2π/ω = 2π√(m/k)
- f:频率 = 1/T = ω/2π
振动示意:
x(t) = A·cos(ωt)
A ────●───────────────── 最大位移
╱ ╲
╱ ╲
0 ─●───────●───────●──── 平衡位置
╲ ╱
╲ ╱
-A ────●───────────────── 最小位移
0 T/4 T/2 3T/4 T
📐 1.2 能量分析
弹簧振子系统涉及动能和势能的相互转换:
能量表达式:
动能:
Ek = ½mv² = ½mω²A²sin²(ωt + φ)
势能:
Ep = ½kx² = ½kA²cos²(ωt + φ)
总能量:
E = Ek + Ep = ½kA² = 常数
能量守恒:振动过程中总能量不变
能量转换示意:
位置 势能 Ep 动能 Ek 总能量 E
─────────────────────────────────────────────
+A (端点) 最大 (100%) 0 100%
↓ 减小 增加 100%
0 (中心) 0 最大 (100%) 100%
↓ 增加 减小 100%
-A (端点) 最大 (100%) 0 100%
能量在动能和势能之间周期性转换
相空间表示:
相空间是 (x, v) 或 (x, p) 构成的空间
对于简谐振动:
x = A·cos(ωt + φ)
v = -Aω·sin(ωt + φ)
相轨迹是椭圆:
(x/A)² + (v/Aω)² = 1
v
↑
─────●─────
╱ │ ╲
● │ ●
──────────┼─────────→ x
● │ ●
╲ │ ╱
─────●─────
🔬 1.3 阻尼振动
实际振动系统总存在阻力,振幅会逐渐减小,这就是阻尼振动。
阻尼力:
粘滞阻力与速度成正比:
F_d = -bv = -b(dx/dt)
其中 b 是阻尼系数
运动方程:
m(d²x/dt²) + b(dx/dt) + kx = 0
设 γ = b/(2m)(阻尼因子),ω₀ = √(k/m)(固有频率):
d²x/dt² + 2γ(dx/dt) + ω₀²x = 0
三种阻尼情况:
| 类型 | 条件 | 解 | 特点 |
|---|---|---|---|
| 🟢 欠阻尼 | γ < ω₀ | x = Ae⁻ᵞᵗcos(ω’t + φ) | 振荡衰减 |
| 🟡 临界阻尼 | γ = ω₀ | x = (A + Bt)e⁻ᵞᵗ | 最快回到平衡 |
| 🔴 过阻尼 | γ > ω₀ | x = Ae⁻ʳ¹ᵗ + Be⁻ʳ²ᵗ | 缓慢回到平衡 |
阻尼振动示意:
欠阻尼振动:
────●─────────────────
╱ ╲ ╱╲ ╱╲ ╱╲
╱ ╲╱ ╲╱ ╲╱
─●────────────────●─
振幅逐渐衰减,仍有振动
临界阻尼:
────●─────────────────
╲
╲
╲
─────────●───────────
最快回到平衡位置,无振动
过阻尼:
────●─────────────────
╲
╲
╲_____________
───────────────────────
缓慢回到平衡位置
🎯 1.4 受迫振动与共振
当系统受到周期性外力作用时,发生受迫振动。
受迫振动方程:
m(d²x/dt²) + b(dx/dt) + kx = F₀cos(ωt)
其中 F₀ 是外力幅值,ω 是外力频率
稳态解:
x(t) = A·cos(ωt - φ)
振幅:
A = F₀ / √[(k - mω²)² + (bω)²]
相位差:
φ = arctan(bω / (k - mω²))
共振现象:
当外力频率接近固有频率时,振幅急剧增大
共振频率:
ω_r = √(ω₀² - 2γ²)
当阻尼很小时,ω_r ≈ ω₀
共振曲线:
振幅 A
↑
│ ╱╲
│ ╱ ╲
│ ╱ ╲
│ ╱ ╲
│──●────────●───→ ω
ω₀ ω_r
阻尼越小,共振峰越尖锐
共振的应用与危害:
| 应用 | 危害 |
|---|---|
| 🎵 乐器发声 | 🌉 桥梁坍塌 |
| 📻 无线电调谐 | 🏢 建筑物震动 |
| 🔬 核磁共振 | ⚙️ 机械损坏 |
| 🎸 声学共振 | 🚗 汽车颠簸 |
🔧 二、弹簧振子系统的 Dart 实现
🧮 2.1 物理模拟引擎
import 'dart:math';
/// 弹簧振子系统
class SpringOscillator {
double mass;
double springConstant;
double dampingCoefficient;
double position;
double velocity;
double externalForceAmplitude;
double externalForceFrequency;
SpringOscillator({
required this.mass,
required this.springConstant,
this.dampingCoefficient = 0.0,
this.position = 0.0,
this.velocity = 0.0,
this.externalForceAmplitude = 0.0,
this.externalForceFrequency = 0.0,
});
double get naturalFrequency => sqrt(springConstant / mass);
double get dampingRatio => dampingCoefficient / (2 * sqrt(springConstant * mass));
double get period => 2 * pi / naturalFrequency;
double kineticEnergy() => 0.5 * mass * velocity * velocity;
double potentialEnergy() => 0.5 * springConstant * position * position;
double totalEnergy() => kineticEnergy() + potentialEnergy();
void update(double dt) {
final springForce = -springConstant * position;
final dampingForce = -dampingCoefficient * velocity;
final externalForce = externalForceAmplitude * cos(externalForceFrequency * _time);
final acceleration = (springForce + dampingForce + externalForce) / mass;
velocity += acceleration * dt;
position += velocity * dt;
_time += dt;
}
double _time = 0;
void reset({double? initialPosition, double? initialVelocity}) {
position = initialPosition ?? 0.0;
velocity = initialVelocity ?? 0.0;
_time = 0;
}
OscillationType get oscillationType {
final ratio = dampingRatio;
if (ratio < 1.0) return OscillationType.underdamped;
if (ratio == 1.0) return OscillationType.critical;
return OscillationType.overdamped;
}
}
enum OscillationType {
underdamped,
critical,
overdamped,
}
/// 多弹簧耦合系统
class CoupledSpringSystem {
final List<SpringOscillator> oscillators;
final List<double> couplingConstants;
CoupledSpringSystem({
required this.oscillators,
required this.couplingConstants,
});
void update(double dt) {
final accelerations = List<double>.filled(oscillators.length, 0);
for (int i = 0; i < oscillators.length; i++) {
final osc = oscillators[i];
accelerations[i] = -osc.springConstant * osc.position / osc.mass;
accelerations[i] -= osc.dampingCoefficient * osc.velocity / osc.mass;
if (i > 0) {
final coupling = couplingConstants[i - 1];
accelerations[i] += coupling * (oscillators[i - 1].position - osc.position) / osc.mass;
}
if (i < oscillators.length - 1) {
final coupling = couplingConstants[i];
accelerations[i] += coupling * (oscillators[i + 1].position - osc.position) / osc.mass;
}
}
for (int i = 0; i < oscillators.length; i++) {
oscillators[i].velocity += accelerations[i] * dt;
oscillators[i].position += oscillators[i].velocity * dt;
}
}
}
⚡ 2.2 可视化组件
import 'package:flutter/material.dart';
/// 弹簧可视化绘制器
class SpringVisualizer {
static Path drawSpring({
required Offset start,
required Offset end,
required int coils,
required double amplitude,
}) {
final path = Path();
path.moveTo(start.dx, start.dy);
final dx = end.dx - start.dx;
final dy = end.dy - start.dy;
final length = sqrt(dx * dx + dy * dy);
final angle = atan2(dy, dx);
final segmentLength = length / (coils * 2 + 2);
for (int i = 0; i <= coils * 2; i++) {
final t = (i + 1) / (coils * 2 + 2);
final x = start.dx + dx * t;
final y = start.dy + dy * t;
final offsetX = amplitude * sin(i * pi) * cos(angle + pi / 2);
final offsetY = amplitude * sin(i * pi) * sin(angle + pi / 2);
path.lineTo(x + offsetX, y + offsetY);
}
path.lineTo(end.dx, end.dy);
return path;
}
static void drawMass(
Canvas canvas,
Offset center,
double size,
Color color,
) {
final rect = Rect.fromCenter(center: center, width: size, height: size);
canvas.drawRRect(
RRect.fromRectAndRadius(rect, const Radius.circular(8)),
Paint()..color = color,
);
canvas.drawRRect(
RRect.fromRectAndRadius(rect, const Radius.circular(8)),
Paint()
..color = Colors.white.withOpacity(0.3)
..style = PaintingStyle.stroke
..strokeWidth = 2,
);
}
static void drawDamper(
Canvas canvas,
Offset start,
Offset end,
double width,
Color color,
) {
final paint = Paint()
..color = color
..style = PaintingStyle.stroke
..strokeWidth = 2;
final mid = Offset((start.dx + end.dx) / 2, (start.dy + end.dy) / 2);
canvas.drawLine(start, mid, paint);
canvas.drawLine(end, mid, paint);
final rect = Rect.fromCenter(center: mid, width: width, height: width * 0.6);
canvas.drawRect(rect, paint);
}
}
🎨 2.3 动画控制器
import 'package:flutter/material.dart';
/// 弹簧振子动画控制器
class SpringAnimationController extends ChangeNotifier {
late SpringOscillator _oscillator;
double _time = 0;
bool _isRunning = true;
double _timeScale = 1.0;
final List<_HistoryPoint> _history = [];
final int _maxHistoryLength = 500;
SpringAnimationController({
double mass = 1.0,
double springConstant = 10.0,
double dampingCoefficient = 0.5,
double initialPosition = 1.0,
double initialVelocity = 0.0,
}) {
_oscillator = SpringOscillator(
mass: mass,
springConstant: springConstant,
dampingCoefficient: dampingCoefficient,
position: initialPosition,
velocity: initialVelocity,
);
}
SpringOscillator get oscillator => _oscillator;
double get time => _time;
bool get isRunning => _isRunning;
double get timeScale => _timeScale;
List<_HistoryPoint> get history => _history;
void update(double dt) {
if (_isRunning) {
_oscillator.update(dt * _timeScale);
_time += dt * _timeScale;
_history.add(_HistoryPoint(
time: _time,
position: _oscillator.position,
velocity: _oscillator.velocity,
kineticEnergy: _oscillator.kineticEnergy(),
potentialEnergy: _oscillator.potentialEnergy(),
));
if (_history.length > _maxHistoryLength) {
_history.removeAt(0);
}
notifyListeners();
}
}
void toggleRunning() {
_isRunning = !_isRunning;
notifyListeners();
}
void setTimeScale(double scale) {
_timeScale = scale.clamp(0.1, 5.0);
notifyListeners();
}
void setMass(double value) {
_oscillator.mass = value.clamp(0.1, 10.0);
notifyListeners();
}
void setSpringConstant(double value) {
_oscillator.springConstant = value.clamp(0.1, 100.0);
notifyListeners();
}
void setDampingCoefficient(double value) {
_oscillator.dampingCoefficient = value.clamp(0.0, 10.0);
notifyListeners();
}
void setInitialPosition(double value) {
_oscillator.position = value;
_oscillator.velocity = 0;
_time = 0;
_history.clear();
notifyListeners();
}
void reset() {
_oscillator.reset(initialPosition: 1.0, initialVelocity: 0.0);
_time = 0;
_history.clear();
notifyListeners();
}
}
class _HistoryPoint {
final double time;
final double position;
final double velocity;
final double kineticEnergy;
final double potentialEnergy;
_HistoryPoint({
required this.time,
required this.position,
required this.velocity,
required this.kineticEnergy,
required this.potentialEnergy,
});
}
📦 三、完整示例代码
以下是完整的弹簧振子系统可视化示例代码:
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(const SpringApp());
}
class SpringApp extends StatelessWidget {
const SpringApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: '弹簧振子系统',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.green, brightness: Brightness.dark),
useMaterial3: true,
),
home: const SpringHomePage(),
debugShowCheckedModeBanner: false,
);
}
}
class SpringHomePage extends StatelessWidget {
const SpringHomePage({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.sync, color: Colors.green,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SimpleHarmonicDemo()))),
_buildCard(context, title: '阻尼振动', description: '能量耗散过程', icon: Icons.trending_down, color: Colors.orange,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const DampedOscillationDemo()))),
_buildCard(context, title: '共振现象', description: '受迫振动与共振', icon: Icons.graphic_eq, color: Colors.purple,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const ResonanceDemo()))),
_buildCard(context, title: '耦合振子', description: '多弹簧系统', icon: Icons.link, color: Colors.teal,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const CoupledOscillatorDemo()))),
],
),
);
}
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 SpringOscillator {
double mass, springConstant, dampingCoefficient, position, velocity;
double externalAmplitude = 0, externalFrequency = 0;
double _time = 0;
SpringOscillator({required this.mass, required this.springConstant, this.dampingCoefficient = 0, this.position = 0, this.velocity = 0});
double get naturalFrequency => sqrt(springConstant / mass);
double get dampingRatio => dampingCoefficient / (2 * sqrt(springConstant * mass));
double get period => 2 * pi / naturalFrequency;
double kineticEnergy() => 0.5 * mass * velocity * velocity;
double potentialEnergy() => 0.5 * springConstant * position * position;
double totalEnergy() => kineticEnergy() + potentialEnergy();
void update(double dt) {
final springForce = -springConstant * position;
final dampingForce = -dampingCoefficient * velocity;
final externalForce = externalAmplitude * cos(externalFrequency * _time);
final acceleration = (springForce + dampingForce + externalForce) / mass;
velocity += acceleration * dt;
position += velocity * dt;
_time += dt;
}
void reset({double? pos, double? vel}) { position = pos ?? 0; velocity = vel ?? 0; _time = 0; }
}
/// 简谐振动演示
class SimpleHarmonicDemo extends StatefulWidget {
const SimpleHarmonicDemo({super.key});
State<SimpleHarmonicDemo> createState() => _SimpleHarmonicDemoState();
}
class _SimpleHarmonicDemoState extends State<SimpleHarmonicDemo> with SingleTickerProviderStateMixin {
late AnimationController _ctrl;
late SpringOscillator _osc;
double _time = 0;
final List<Offset> _trajectory = [];
void initState() {
super.initState();
_osc = SpringOscillator(mass: 1, springConstant: 10, position: 1);
_ctrl = AnimationController(vsync: this, duration: const Duration(milliseconds: 16))..repeat();
_ctrl.addListener(_update);
}
void _update() {
_osc.update(0.016);
_time += 0.016;
_trajectory.add(Offset(_time, _osc.position));
if (_trajectory.length > 300) _trajectory.removeAt(0);
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: Row(children: [
Expanded(flex: 2, child: _buildSpringView()),
Expanded(child: _buildPhaseSpace()),
])),
_buildGraph(),
_buildControls(),
]),
);
}
Widget _buildSpringView() {
return CustomPaint(
painter: SpringViewPainter(_osc.position, _time),
size: Size.infinite,
);
}
Widget _buildPhaseSpace() {
return CustomPaint(
painter: PhaseSpacePainter(_osc.position, _osc.velocity),
size: Size.infinite,
);
}
Widget _buildGraph() {
return SizedBox(height: 120, child: CustomPaint(painter: TrajectoryPainter(_trajectory), size: Size.infinite));
}
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: _osc.mass, min: 0.1, max: 5, onChanged: (v) => setState(() => _osc.mass = v))),
Text(_osc.mass.toStringAsFixed(2), style: const TextStyle(color: Colors.green)),
]),
Row(children: [
const Text('劲度: ', style: TextStyle(color: Colors.white70)),
Expanded(child: Slider(value: _osc.springConstant, min: 1, max: 50, onChanged: (v) => setState(() => _osc.springConstant = v))),
Text(_osc.springConstant.toStringAsFixed(1), style: const TextStyle(color: Colors.green)),
]),
Text('周期: ${_osc.period.toStringAsFixed(2)}s 能量: ${_osc.totalEnergy().toStringAsFixed(3)}J', style: const TextStyle(color: Colors.white70)),
]),
);
}
}
class SpringViewPainter extends CustomPainter {
final double position;
final double time;
SpringViewPainter(this.position, this.time);
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final equilibriumX = size.width * 0.3;
final massX = equilibriumX + position * 50;
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
// 固定墙
canvas.drawRect(Rect.fromLTWH(10, center.dy - 60, 20, 120), Paint()..color = Colors.grey);
// 弹簧
_drawSpring(canvas, Offset(30, center.dy), Offset(massX - 30, center.dy));
// 质量块
final hue = 120 + sin(time * 2) * 30;
canvas.drawRRect(RRect.fromRectAndRadius(Rect.fromCenter(center: Offset(massX, center.dy), width: 60, height: 60), const Radius.circular(8)),
Paint()..color = HSVColor.fromAHSV(1, hue, 0.7, 0.9).toColor());
// 平衡位置标记
canvas.drawLine(Offset(equilibriumX, center.dy - 80), Offset(equilibriumX, center.dy + 80), Paint()..color = Colors.white24..strokeWidth = 1);
// 位移标注
final tp = TextPainter(text: TextSpan(text: 'x = ${position.toStringAsFixed(2)}', style: const TextStyle(color: Colors.white70, fontSize: 12)), textDirection: TextDirection.ltr)..layout();
tp.paint(canvas, Offset(massX - 20, center.dy + 50));
}
void _drawSpring(Canvas canvas, Offset start, Offset end) {
final path = Path();
path.moveTo(start.dx, start.dy);
final coils = 12;
final amplitude = 15.0;
final length = end.dx - start.dx;
for (int i = 0; i <= coils * 2; i++) {
final t = i / (coils * 2);
final x = start.dx + length * t;
final y = start.dy + amplitude * sin(i * pi);
path.lineTo(x, y);
}
path.lineTo(end.dx, end.dy);
canvas.drawPath(path, Paint()..color = Colors.green..style = PaintingStyle.stroke..strokeWidth = 3);
}
bool shouldRepaint(covariant SpringViewPainter old) => true;
}
class PhaseSpacePainter extends CustomPainter {
final double x, v;
PhaseSpacePainter(this.x, this.v);
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
// 坐标轴
canvas.drawLine(Offset(0, center.dy), Offset(size.width, center.dy), Paint()..color = Colors.white24);
canvas.drawLine(Offset(center.dx, 0), Offset(center.dx, size.height), Paint()..color = Colors.white24);
// 当前点
final pointX = center.dx + x * 20;
final pointY = center.dy - v * 20;
canvas.drawCircle(Offset(pointX, pointY), 6, Paint()..color = Colors.green);
// 标签
final tp = TextPainter(text: const TextSpan(text: '相空间', style: TextStyle(color: Colors.white70, fontSize: 10)), textDirection: TextDirection.ltr)..layout();
tp.paint(canvas, const Offset(8, 8));
}
bool shouldRepaint(covariant PhaseSpacePainter old) => true;
}
class TrajectoryPainter extends CustomPainter {
final List<Offset> trajectory;
TrajectoryPainter(this.trajectory);
void paint(Canvas canvas, Size size) {
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
if (trajectory.length < 2) return;
final path = Path();
final scaleX = size.width / 5;
final scaleY = size.height / 4;
final centerY = size.height / 2;
for (int i = 0; i < trajectory.length; i++) {
final x = (trajectory[i].dx - trajectory.first.dx) * scaleX / 60;
final y = centerY - trajectory[i].dy * scaleY;
if (i == 0) path.moveTo(x, y);
else path.lineTo(x, y);
}
canvas.drawPath(path, Paint()..color = Colors.green..style = PaintingStyle.stroke..strokeWidth = 2);
}
bool shouldRepaint(covariant TrajectoryPainter old) => true;
}
/// 阻尼振动演示
class DampedOscillationDemo extends StatefulWidget {
const DampedOscillationDemo({super.key});
State<DampedOscillationDemo> createState() => _DampedOscillationDemoState();
}
class _DampedOscillationDemoState extends State<DampedOscillationDemo> with SingleTickerProviderStateMixin {
late AnimationController _ctrl;
late SpringOscillator _osc;
double _damping = 0.5;
final List<double> _energies = [];
void initState() {
super.initState();
_osc = SpringOscillator(mass: 1, springConstant: 10, dampingCoefficient: _damping, position: 1);
_ctrl = AnimationController(vsync: this, duration: const Duration(milliseconds: 16))..repeat();
_ctrl.addListener(_update);
}
void _update() {
_osc.update(0.016);
_energies.add(_osc.totalEnergy());
if (_energies.length > 200) _energies.removeAt(0);
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: DampedPainter(_osc.position, _osc.dampingRatio), size: Size.infinite)),
SizedBox(height: 100, child: CustomPaint(painter: EnergyPainter(_energies), 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: _damping, min: 0, max: 3, onChanged: (v) => setState(() { _damping = v; _osc.dampingCoefficient = v; _osc.reset(pos: 1); _energies.clear(); })),
),
Text(_damping.toStringAsFixed(2), style: const TextStyle(color: Colors.orange)),
]),
Text(_getTypeText(), style: TextStyle(color: _getTypeColor())),
]),
);
}
String _getTypeText() {
final r = _osc.dampingRatio;
if (r < 1) return '欠阻尼 - 振荡衰减';
if (r == 1) return '临界阻尼 - 最快回归';
return '过阻尼 - 缓慢回归';
}
Color _getTypeColor() {
final r = _osc.dampingRatio;
if (r < 1) return Colors.green;
if (r == 1) return Colors.yellow;
return Colors.red;
}
}
class DampedPainter extends CustomPainter {
final double position;
final double dampingRatio;
DampedPainter(this.position, this.dampingRatio);
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
// 包络线
final envelope = exp(-dampingRatio * 2);
final paint = Paint()..color = Colors.orange.withOpacity(0.3)..style = PaintingStyle.stroke;
canvas.drawLine(Offset(center.dx - 150, center.dy - 50 * envelope), Offset(center.dx + 150, center.dy - 50 * envelope), paint);
canvas.drawLine(Offset(center.dx - 150, center.dy + 50 * envelope), Offset(center.dx + 150, center.dy + 50 * envelope), paint);
// 质量块
final massX = center.dx + position * 50;
canvas.drawRRect(RRect.fromRectAndRadius(Rect.fromCenter(center: Offset(massX, center.dy), width: 50, height: 50), const Radius.circular(8)),
Paint()..color = Colors.orange);
}
bool shouldRepaint(covariant DampedPainter old) => true;
}
class EnergyPainter extends CustomPainter {
final List<double> energies;
EnergyPainter(this.energies);
void paint(Canvas canvas, Size size) {
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
if (energies.length < 2) return;
final path = Path();
final maxE = energies.reduce(max) * 1.2;
for (int i = 0; i < energies.length; i++) {
final x = i * size.width / 200;
final y = size.height - energies[i] / maxE * size.height * 0.8;
if (i == 0) path.moveTo(x, y);
else path.lineTo(x, y);
}
canvas.drawPath(path, Paint()..color = Colors.purple..style = PaintingStyle.stroke..strokeWidth = 2);
}
bool shouldRepaint(covariant EnergyPainter old) => true;
}
/// 共振演示
class ResonanceDemo extends StatefulWidget {
const ResonanceDemo({super.key});
State<ResonanceDemo> createState() => _ResonanceDemoState();
}
class _ResonanceDemoState extends State<ResonanceDemo> with SingleTickerProviderStateMixin {
late AnimationController _ctrl;
late SpringOscillator _osc;
double _driveFreq = 3.0;
void initState() {
super.initState();
_osc = SpringOscillator(mass: 1, springConstant: 10, dampingCoefficient: 0.3, position: 0);
_osc.externalAmplitude = 0.5;
_osc.externalFrequency = _driveFreq;
_ctrl = AnimationController(vsync: this, duration: const Duration(milliseconds: 16))..repeat();
_ctrl.addListener(_update);
}
void _update() { _osc.update(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: ResonancePainter(_osc.position, _osc.naturalFrequency, _driveFreq), 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: _driveFreq, min: 0.5, max: 6, onChanged: (v) => setState(() { _driveFreq = v; _osc.externalFrequency = v; _osc.reset(); }))),
Text('${_driveFreq.toStringAsFixed(2)} Hz', style: const TextStyle(color: Colors.purple)),
]),
Text('固有频率: ${_osc.naturalFrequency.toStringAsFixed(2)} Hz', style: const TextStyle(color: Colors.white70)),
Text(_isNearResonance() ? '⚠️ 接近共振!振幅增大' : '远离共振', style: TextStyle(color: _isNearResonance() ? Colors.red : Colors.green)),
]),
);
}
bool _isNearResonance() => (_driveFreq - _osc.naturalFrequency).abs() < 0.5;
}
class ResonancePainter extends CustomPainter {
final double position;
final double naturalFreq;
final double driveFreq;
ResonancePainter(this.position, this.naturalFreq, this.driveFreq);
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
// 振幅指示
final amplitude = position.abs() * 30;
canvas.drawRect(Rect.fromLTWH(center.dx - 20, center.dy - amplitude, 40, amplitude * 2), Paint()..color = Colors.purple.withOpacity(0.5));
// 质量块
final massY = center.dy + position * 50;
canvas.drawRRect(RRect.fromRectAndRadius(Rect.fromCenter(center: Offset(center.dx, massY), width: 50, height: 30), const Radius.circular(8)),
Paint()..color = Colors.purple);
// 频率指示
final tp = TextPainter(text: TextSpan(text: 'ω₀=${naturalFreq.toStringAsFixed(2)} ω=${driveFreq.toStringAsFixed(2)}', style: const TextStyle(color: Colors.white70, fontSize: 12)), textDirection: TextDirection.ltr)..layout();
tp.paint(canvas, const Offset(8, 8));
}
bool shouldRepaint(covariant ResonancePainter old) => true;
}
/// 耦合振子演示
class CoupledOscillatorDemo extends StatefulWidget {
const CoupledOscillatorDemo({super.key});
State<CoupledOscillatorDemo> createState() => _CoupledOscillatorDemoState();
}
class _CoupledOscillatorDemoState extends State<CoupledOscillatorDemo> with SingleTickerProviderStateMixin {
late AnimationController _ctrl;
final List<double> _positions = [1.0, 0.0, 0.0];
final List<double> _velocities = [0.0, 0.0, 0.0];
final double _k = 10.0, _coupling = 5.0, _m = 1.0;
void initState() {
super.initState();
_ctrl = AnimationController(vsync: this, duration: const Duration(milliseconds: 16))..repeat();
_ctrl.addListener(_update);
}
void _update() {
final acc = List<double>.filled(3, 0);
for (int i = 0; i < 3; i++) {
acc[i] = -_k * _positions[i] / _m;
if (i > 0) acc[i] += _coupling * (_positions[i - 1] - _positions[i]) / _m;
if (i < 2) acc[i] += _coupling * (_positions[i + 1] - _positions[i]) / _m;
}
for (int i = 0; i < 3; i++) {
_velocities[i] += acc[i] * 0.016;
_positions[i] += _velocities[i] * 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: CoupledPainter(_positions), 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: Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
ElevatedButton(onPressed: () => setState(() { _positions[0] = 1; _positions[1] = 0; _positions[2] = 0; for (int i = 0; i < 3; i++) _velocities[i] = 0; }), child: const Text('模式1')),
ElevatedButton(onPressed: () => setState(() { _positions[0] = 1; _positions[1] = 0; _positions[2] = -1; for (int i = 0; i < 3; i++) _velocities[i] = 0; }), child: const Text('模式2')),
ElevatedButton(onPressed: () => setState(() { _positions[0] = 1; _positions[1] = 1; _positions[2] = 1; for (int i = 0; i < 3; i++) _velocities[i] = 0; }), child: const Text('模式3')),
]),
);
}
}
class CoupledPainter extends CustomPainter {
final List<double> positions;
CoupledPainter(this.positions);
void paint(Canvas canvas, Size size) {
final centerY = size.height / 2;
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
final colors = [Colors.red, Colors.green, Colors.blue];
for (int i = 0; i < 3; i++) {
final x = size.width * (0.25 + i * 0.25) + positions[i] * 30;
// 弹簧连接
if (i < 2) {
final nextX = size.width * (0.25 + (i + 1) * 0.25) + positions[i + 1] * 30;
canvas.drawLine(Offset(x + 25, centerY), Offset(nextX - 25, centerY), Paint()..color = Colors.white30..strokeWidth = 2);
}
// 质量块
canvas.drawRRect(RRect.fromRectAndRadius(Rect.fromCenter(center: Offset(x, centerY), width: 50, height: 50), const Radius.circular(8)),
Paint()..color = colors[i]);
}
}
bool shouldRepaint(covariant CoupledPainter old) => true;
}
📝 四、数学原理深入解析
📐 4.1 微分方程求解
简谐振动方程求解:
d²x/dt² + ω²x = 0
特征方程:r² + ω² = 0
特征根:r = ±iω
通解:
x(t) = C₁cos(ωt) + C₂sin(ωt)
= A·cos(ωt + φ)
其中:
A = √(C₁² + C₂²)
φ = arctan(-C₂/C₁)
阻尼振动方程求解:
d²x/dt² + 2γ(dx/dt) + ω₀²x = 0
特征方程:r² + 2γr + ω₀² = 0
特征根:r = -γ ± √(γ² - ω₀²)
三种情况:
1. 欠阻尼 (γ < ω₀):
r = -γ ± iω'
x = Ae⁻ᵞᵗcos(ω't + φ)
ω' = √(ω₀² - γ²)
2. 临界阻尼 (γ = ω₀):
r = -γ(重根)
x = (A + Bt)e⁻ᵞᵗ
3. 过阻尼 (γ > ω₀):
r = -γ ± √(γ² - ω₀²)
x = Ae⁻ʳ¹ᵗ + Be⁻ʳ²ᵗ
🔄 4.2 品质因数 Q
品质因数定义:
Q = ω₀ / (2γ) = √(km) / b
物理意义:
- Q 越大,阻尼越小
- Q 越大,共振峰越尖锐
- Q 越大,能量衰减越慢
能量衰减:
E(t) = E₀e⁻²ᵞᵗ = E₀e⁻ᵗ/τ
时间常数:τ = 1/(2γ) = Q/ω₀
振动次数:
在能量衰减到 1/e 之前,振动次数 ≈ Q/(2π)
🌸 4.3 傅里叶分析
振动的频谱表示:
任何周期运动都可以分解为简谐振动的叠加:
x(t) = Σ Aₙcos(nωt + φₙ)
傅里叶变换:
X(ω) = ∫ x(t)e⁻ⁱᵒᵗdt
对于阻尼振动:
X(ω) ∝ 1 / [(ω₀² - ω²) + i(2γω)]
功率谱密度:
|X(ω)|² ∝ 1 / [(ω₀² - ω²)² + (2γω)²]
🎯 4.4 相空间与混沌
相空间动力学:
对于受迫阻尼振子:
dx/dt = v
dv/dt = -ω₀²x - 2γv + (F₀/m)cos(ωt)
这是二维非自治系统
可以扩展为三维自治系统:
dx/dt = v
dv/dt = -ω₀²x - 2γv + (F₀/m)cos(θ)
dθ/dt = ω
当驱动力足够大时,可能出现混沌
🔬 五、高级应用场景
🎨 5.1 物理仿真
刚体动力学:
class RigidBodySimulation {
final List<SpringOscillator> oscillators;
final List<List<double>> couplingMatrix;
void update(double dt) {
// 计算所有振子的加速度
final accelerations = <double>[];
for (int i = 0; i < oscillators.length; i++) {
double acc = -oscillators[i].springConstant * oscillators[i].position / oscillators[i].mass;
for (int j = 0; j < oscillators.length; j++) {
if (couplingMatrix[i][j] != 0) {
acc += couplingMatrix[i][j] * (oscillators[j].position - oscillators[i].position) / oscillators[i].mass;
}
}
accelerations.add(acc);
}
// 更新速度和位置
for (int i = 0; i < oscillators.length; i++) {
oscillators[i].velocity += accelerations[i] * dt;
oscillators[i].position += oscillators[i].velocity * dt;
}
}
}
🌐 5.2 音频合成
弹簧混响模型:
class SpringReverb {
final List<SpringOscillator> springs;
SpringReverb(int count) : springs = List.generate(count, (i) => SpringOscillator(mass: 1, springConstant: 10 + i * 2, dampingCoefficient: 0.1));
double process(double input) {
double output = 0;
for (final spring in springs) {
spring.externalAmplitude = input;
spring.externalFrequency = spring.naturalFrequency;
spring.update(0.001);
output += spring.position;
}
return output / springs.length;
}
}
📱 5.3 鸿蒙多端适配
性能优化配置:
class SpringSimulationConfig {
static int getOptimalOscillatorCount(BuildContext context) {
final performance = DevicePerformance.getLevel();
switch (performance) {
case PerformanceLevel.high: return 20;
case PerformanceLevel.medium: return 10;
case PerformanceLevel.low: return 5;
}
}
static double getOptimalTimeStep() {
return 0.016; // 60 FPS
}
}
📊 六、性能优化策略
⚡ 6.1 数值积分优化
龙格-库塔方法:
class RK4Integrator {
static void integrate(SpringOscillator osc, double dt) {
final k1v = _acceleration(osc, osc.position, osc.velocity);
final k1x = osc.velocity;
final k2v = _acceleration(osc, osc.position + k1x * dt / 2, osc.velocity + k1v * dt / 2);
final k2x = osc.velocity + k1v * dt / 2;
final k3v = _acceleration(osc, osc.position + k2x * dt / 2, osc.velocity + k2v * dt / 2);
final k3x = osc.velocity + k2v * dt / 2;
final k4v = _acceleration(osc, osc.position + k3x * dt, osc.velocity + k3v * dt);
final k4x = osc.velocity + k3v * dt;
osc.velocity += (k1v + 2 * k2v + 2 * k3v + k4v) * dt / 6;
osc.position += (k1x + 2 * k2x + 2 * k3x + k4x) * dt / 6;
}
static double _acceleration(SpringOscillator osc, double x, double v) {
return (-osc.springConstant * x - osc.dampingCoefficient * v) / osc.mass;
}
}
💾 6.2 历史数据管理
环形缓冲区:
class RingBuffer<T> {
final List<T?> _buffer;
int _head = 0;
int _count = 0;
RingBuffer(int size) : _buffer = List.filled(size, null);
void add(T item) {
_buffer[_head] = item;
_head = (_head + 1) % _buffer.length;
if (_count < _buffer.length) _count++;
}
List<T> toList() {
final result = <T>[];
for (int i = 0; i < _count; i++) {
final idx = (_head - _count + i + _buffer.length) % _buffer.length;
result.add(_buffer[idx]!);
}
return result;
}
}
🎓 七、学习资源与拓展
📚 推荐阅读
| 主题 | 资源 | 难度 |
|---|---|---|
| 经典力学 | 《费曼物理学讲义》第一卷 | ⭐⭐ |
| 振动理论 | 《振动理论》- 铁摩辛柯 | ⭐⭐⭐ |
| 微分方程 | 《常微分方程》 | ⭐⭐ |
| 混沌理论 | 《混沌:开创新科学》 | ⭐⭐ |
🔗 相关项目
- PhET Simulations:物理仿真教育项目
- Algodoo:物理沙盒软件
- Box2D:2D 物理引擎
📝 八、总结
本篇文章深入探讨了弹簧振子系统的数学原理及其在 Flutter 中的可视化实现。
✅ 核心知识点回顾
| 知识点 | 说明 |
|---|---|
| 🎯 简谐振动 | 胡克定律与周期运动 |
| 📉 阻尼振动 | 能量耗散与三种阻尼 |
| 📊 共振现象 | 受迫振动与振幅放大 |
| 🔗 耦合振子 | 多体系统与简正模式 |
| ⚡ 数值模拟 | 欧拉法与龙格-库塔法 |
⭐ 最佳实践要点
- ✅ 使用 RK4 积分提高精度
- ✅ 合理设置阻尼系数
- ✅ 注意共振频率匹配
- ✅ 使用环形缓冲区管理历史数据
🚀 进阶方向
- 🔮 非线性振动与混沌
- ✨ 多自由度系统
- 📊 实时频谱分析
- 🎵 音频效果器应用
💡 提示:本文代码基于 Flutter for Harmony 开发,可在鸿蒙设备上流畅运行。
更多推荐



所有评论(0)