
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
🌀 一、螺旋与黄金分割:数学之美
📚 1.1 黄金比例的发现
黄金比例(Golden Ratio),又称黄金分割,是一个无理数,通常用希腊字母 φ(phi)表示,其值约为 1.618033988749…
数学定义:
黄金比例 φ = (1 + √5) / 2 ≈ 1.618
满足方程:φ² = φ + 1
几何意义:
将线段分为两部分,使得整体与较大部分之比
等于较大部分与较小部分之比
A─────────────B─────C
|<─── a ─────>|<- b->|
(a + b) / a = a / b = φ
历史渊源:
公元前300年:欧几里得在《几何原本》中首次描述
公元前5世纪:古希腊建筑帕特农神庙运用此比例
1509年:卢卡·帕乔利著《神圣比例》
19世纪:德国数学家马丁·欧姆正式命名"黄金分割"
📐 1.2 斐波那契数列与黄金比例
斐波那契数列与黄金比例有着深刻的数学联系:
斐波那契数列定义:
F(n) = F(n-1) + F(n-2)
F(0) = 0, F(1) = 1
数列:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144…
黄金比例收敛:
相邻两项之比趋近于黄金比例:
1/1 = 1
2/1 = 2
3/2 = 1.5
5/3 ≈ 1.667
8/5 = 1.6
13/8 = 1.625
21/13 ≈ 1.615
34/21 ≈ 1.619
55/34 ≈ 1.618 ← 趋近 φ
通项公式(比内公式):
F(n) = (φⁿ – (-φ)⁻ⁿ) / √5
斐波那契矩形:
用斐波那契数列构建的矩形:
┌─┬─┬───┬───────┐
│1│1│ 2 │ 3 │
├─┴─┼───┤ │
│ │ │ │
│ 1 │ ├───────┤
│ │ │ 5 │
└───┴───┴───────┘
每个正方形的边长都是斐波那契数
🔬 1.3 螺旋曲线家族
螺旋是自然界中最优美的曲线形态之一:
| 🌀 阿基米德螺旋 | r = a + bθ | 等距螺旋,相邻圈间距相等 |
| 🌻 黄金螺旋 | r = a·e^(bθ),b = ln(φ)/(π/2) | 增长比例符合黄金分割 |
| 📡 双曲螺旋 | r = a/θ | 向中心无限逼近 |
| ⚛️ 费马螺旋 | r² = a²θ | 等面积螺旋 |
| 🔭 对数螺旋 | r = a·e^(bθ) | 自然界最常见的螺旋 |
螺旋对比示意:
阿基米德螺旋: 黄金螺旋: 费马螺旋:
╭───╮ ╭────╮ ╭────╮
╭╯ ╰╮ ╭╯ ╰╮ ╭╯ ⬤ ╰╮
╭╯ ╰╮ ╭╯ ⬤ ╰╮ ╭╯ ╰╮
╭╯ ╰╮ ╭╯ ╰╮ ╭╯ ⬤ ╰╮
╭╯ ╰╮ ╭╯ ╰╮ ╭╯ ╰╮
╰╮ ╭╯ ╰╮ ╭╯ ╰╮ ⬤ ╭╯
╰╮ ╭╯ ╰╮ ╭╯ ╰╮ ╭╯
╰╮ ╭╯ ╰╮ ⬤ ╭╯ ╰╮ ⬤ ╭╯
╰╮ ╭╯ ╰╮ ╭╯ ╰────╯
╰───╯ ╰────╯
等间距扩展 按黄金比例扩展 等面积扩展
🎯 1.4 黄金角度与自然排列
黄金角度:
黄金角度 = 360° × (1 – 1/φ) ≈ 137.507764°
这是向日葵种子、松果鳞片的排列角度
确保每个新元素都不会与之前的完全重叠
最大化利用空间
向日葵种子排列:
· · · · ·
· · · · · · ·
· · · · ⬤ · · ·
· · · · · · · · ·
· · · · · · · · · ·
· · · · · · · · ·
· · · · · · · ·
· · · · · · ·
· · · · ·
每颗种子间隔 137.5°
形成费马螺旋图案
自然界中的斐波那契数:
| 🌻 向日葵 | 34, 55, 89 | 种子螺旋数 |
| 🌲 松果 | 8, 13 | 鳞片螺旋 |
| 🌸 花瓣 | 3, 5, 8, 13 | 花瓣数量 |
| 🐚 鹦鹉螺 | 连续 | 壳室生长 |
| 🌀 银河系 | 近似 | 旋臂结构 |
🔧 二、螺旋曲线的 Dart 实现
🧮 2.1 黄金比例常量与工具类
import 'dart:math';
import 'dart:typed_data';
/// 黄金比例常量
const double phi = (1 + sqrt5) / 2;
const double sqrt5 = 2.23606797749979;
const double goldenAngle = 2 * pi * (1 – 1 / phi);
const double goldenAngleDeg = 360 * (1 – 1 / phi);
/// 黄金比例计算器
class GoldenRatio {
/// 黄金比例值
static const double value = phi;
/// 黄金比例共轭
static const double conjugate = 1 / phi;
/// 计算黄金分割点
static double goldenSection(double total) {
return total * conjugate;
}
/// 生成斐波那契数列
static List<int> fibonacci(int n) {
if (n <= 0) return [];
if (n == 1) return [1];
if (n == 2) return [1, 1];
final result = [1, 1];
for (int i = 2; i < n; i++) {
result.add(result[i – 1] + result[i – 2]);
}
return result;
}
/// 计算第 n 个斐波那契数(比内公式)
static double fibonacciAt(int n) {
return (pow(phi, n) – pow(–phi, –n)) / sqrt5;
}
/// 向日葵种子排列
static List<Offset> sunflowerSeeds({
required Offset center,
required int count,
required double maxRadius,
}) {
final seeds = <Offset>[];
for (int i = 0; i < count; i++) {
final theta = i * goldenAngle;
final r = maxRadius * sqrt(i / count);
seeds.add(Offset(
center.dx + r * cos(theta),
center.dy + r * sin(theta),
));
}
return seeds;
}
/// 生成黄金矩形序列
static List<Rect> goldenRectangles({
required Size initialSize,
required int count,
}) {
final rects = <Rect>[];
double width = initialSize.width;
double height = initialSize.height;
double x = 0, y = 0;
for (int i = 0; i < count; i++) {
rects.add(Rect.fromLTWH(x, y, width, height));
if (width > height) {
x += height;
width -= height;
} else {
y += width;
height -= width;
}
if (width < 1 || height < 1) break;
}
return rects;
}
}
⚡ 2.2 螺旋曲线生成器
/// 螺旋点
class SpiralPoint {
final double r;
final double theta;
final int index;
const SpiralPoint(this.r, this.theta, this.index);
/// 转换为笛卡尔坐标
Offset toOffset(Offset center, {double scale = 1.0}) {
return Offset(
center.dx + r * cos(theta) * scale,
center.dy + r * sin(theta) * scale,
);
}
/// 旋转
SpiralPoint rotate(double angle) => SpiralPoint(r, theta + angle, index);
/// 缩放
SpiralPoint scale(double factor) => SpiralPoint(r * factor, theta, index);
}
/// 螺旋生成器基类
abstract class SpiralGenerator {
final double a;
final double b;
const SpiralGenerator({this.a = 1.0, this.b = 1.0});
/// 计算给定角度的半径
double radius(double theta);
/// 生成螺旋点序列
List<SpiralPoint> generate({
required double startAngle,
required double endAngle,
required double angleStep,
}) {
final points = <SpiralPoint>[];
int index = 0;
for (double theta = startAngle; theta <= endAngle; theta += angleStep) {
final r = radius(theta);
points.add(SpiralPoint(r, theta, index++));
}
return points;
}
/// 生成路径
Path toPath(List<SpiralPoint> points, Offset center, {double scale = 1.0}) {
final path = Path();
for (int i = 0; i < points.length; i++) {
final offset = points[i].toOffset(center, scale: scale);
if (i == 0) {
path.moveTo(offset.dx, offset.dy);
} else {
path.lineTo(offset.dx, offset.dy);
}
}
return path;
}
}
/// 阿基米德螺旋
class ArchimedeanSpiral extends SpiralGenerator {
const ArchimedeanSpiral({super.a, super.b});
double radius(double theta) => a + b * theta;
}
/// 黄金螺旋
class GoldenSpiral extends SpiralGenerator {
final double growthFactor;
const GoldenSpiral({
super.a = 1.0,
this.growthFactor = 0.306349,
});
double radius(double theta) => a * exp(growthFactor * theta);
}
/// 对数螺旋
class LogarithmicSpiral extends SpiralGenerator {
final double k;
const LogarithmicSpiral({
super.a = 1.0,
this.k = 0.1,
});
double radius(double theta) => a * exp(k * theta);
}
/// 费马螺旋
class FermatSpiral extends SpiralGenerator {
const FermatSpiral({super.a = 1.0});
double radius(double theta) => a * sqrt(theta.abs());
/// 生成双分支费马螺旋
List<SpiralPoint> generateDouble({
required double maxTheta,
required double angleStep,
}) {
final points = <SpiralPoint>[];
int index = 0;
for (double theta = 0; theta <= maxTheta; theta += angleStep) {
points.add(SpiralPoint(radius(theta), theta, index++));
}
for (double theta = angleStep; theta <= maxTheta; theta += angleStep) {
points.add(SpiralPoint(radius(theta), theta + pi, index++));
}
return points;
}
}
/// 双曲螺旋
class HyperbolicSpiral extends SpiralGenerator {
const HyperbolicSpiral({super.a = 1.0});
double radius(double theta) => theta > 0.01 ? a / theta : a / 0.01;
}
🎨 2.3 黄金矩形与螺旋构造
import 'package:flutter/material.dart';
/// 黄金矩形
class GoldenRectangle {
final double width;
final double height;
final Offset origin;
GoldenRectangle({
required this.width,
this.height = 0,
this.origin = Offset.zero,
}) : height = height == 0 ? width / phi : height;
/// 是否为黄金矩形
bool get isGolden => (width / height – phi).abs() < 0.001;
/// 黄金比例
double get ratio => width / height;
/// 分割出正方形
Rect get square => Rect.fromLTWH(origin.dx, origin.dy, height, height);
/// 分割后剩余的矩形
GoldenRectangle get remainder => GoldenRectangle(
width: width – height,
height: height,
origin: Offset(origin.dx + height, origin.dy),
);
/// 生成递归分割序列
List<GoldenRectangle> subdivide(int depth) {
final result = <GoldenRectangle>[this];
var current = this;
for (int i = 0; i < depth; i++) {
if (current.width < 1 || current.height < 1) break;
current = current.remainder;
result.add(current);
}
return result;
}
/// 生成黄金螺旋路径
Path generateSpiralPath(int depth) {
final path = Path();
var current = this;
for (int i = 0; i < depth; i++) {
final square = current.square;
// 根据方向绘制四分之一圆弧
final startAngle = i * pi / 2;
path.arcTo(
square,
startAngle,
–pi / 2,
false,
);
if (current.width < 1 || current.height < 1) break;
current = current.remainder;
}
return path;
}
}
/// 螺旋可视化控制器
class SpiralVisualizationController extends ChangeNotifier {
double _time = 0;
double _rotation = 0;
double _scale = 1.0;
double _growthRate = 0.306349;
int _turns = 5;
int _seedCount = 200;
double _animationSpeed = 1.0;
SpiralType _type = SpiralType.golden;
Color _primaryColor = Colors.amber;
Color _secondaryColor = Colors.purple;
bool _showGrid = true;
bool _showSeeds = false;
double get time => _time;
double get rotation => _rotation;
double get scale => _scale;
double get growthRate => _growthRate;
int get turns => _turns;
int get seedCount => _seedCount;
double get animationSpeed => _animationSpeed;
SpiralType get type => _type;
Color get primaryColor => _primaryColor;
Color get secondaryColor => _secondaryColor;
bool get showGrid => _showGrid;
bool get showSeeds => _showSeeds;
void update(double dt) {
_time += dt * _animationSpeed;
_rotation += dt * 0.5 * _animationSpeed;
notifyListeners();
}
void setSpiralType(SpiralType type) {
_type = type;
notifyListeners();
}
void setTurns(int turns) {
_turns = turns.clamp(1, 20);
notifyListeners();
}
void setSeedCount(int count) {
_seedCount = count.clamp(10, 500);
notifyListeners();
}
void setGrowthRate(double rate) {
_growthRate = rate.clamp(0.01, 1.0);
notifyListeners();
}
void setAnimationSpeed(double speed) {
_animationSpeed = speed.clamp(0.1, 3.0);
notifyListeners();
}
void toggleGrid() {
_showGrid = !_showGrid;
notifyListeners();
}
void toggleSeeds() {
_showSeeds = !_showSeeds;
notifyListeners();
}
void reset() {
_time = 0;
_rotation = 0;
_scale = 1.0;
notifyListeners();
}
}
enum SpiralType {
archimedean,
golden,
logarithmic,
fermat,
hyperbolic,
}
📦 三、完整示例代码
以下是完整的螺旋与黄金分割可视化示例代码:
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(const SpiralApp());
}
class SpiralApp extends StatelessWidget {
const SpiralApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: '螺旋与黄金分割',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.amber, brightness: Brightness.dark),
useMaterial3: true,
),
home: const SpiralHomePage(),
debugShowCheckedModeBanner: false,
);
}
}
class SpiralHomePage extends StatelessWidget {
const SpiralHomePage({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.all_inclusive,
color: Colors.amber,
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const GoldenSpiralDemo()),
),
),
_buildCard(
context,
title: '螺旋家族',
description: '五种经典螺旋对比',
icon: Icons.show_chart,
color: Colors.purple,
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const SpiralFamilyDemo()),
),
),
_buildCard(
context,
title: '向日葵种子',
description: '黄金角度的空间排列',
icon: Icons.local_florist,
color: Colors.orange,
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const SunflowerDemo()),
),
),
_buildCard(
context,
title: '黄金矩形',
description: '递归分割与螺旋构造',
icon: Icons.crop_square,
color: Colors.teal,
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const GoldenRectangleDemo()),
),
),
_buildCard(
context,
title: '斐波那契动画',
description: '数列的可视化生长',
icon: Icons.animation,
color: Colors.indigo,
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const FibonacciDemo()),
),
),
],
),
);
}
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]),
],
),
),
),
);
}
}
const double phi = (1 + sqrt5) / 2;
const double sqrt5 = 2.23606797749979;
const double goldenAngle = 2 * pi * (1 – 1 / phi);
class GoldenSpiralDemo extends StatefulWidget {
const GoldenSpiralDemo({super.key});
State<GoldenSpiralDemo> createState() => _GoldenSpiralDemoState();
}
class _GoldenSpiralDemoState extends State<GoldenSpiralDemo>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
double _time = 0;
int _turns = 6;
double _growthRate = 0.306349;
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 16),
)..repeat();
_controller.addListener(() {
_time += 0.016;
setState(() {});
});
}
void dispose() {
_controller.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('黄金螺旋')),
body: Column(
children: [
Expanded(
child: CustomPaint(
painter: GoldenSpiralPainter(_time, _turns, _growthRate),
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: _turns.toDouble(),
min: 2,
max: 12,
divisions: 10,
onChanged: (v) => setState(() => _turns = v.toInt()),
),
),
Text('$_turns', style: const TextStyle(color: Colors.amber)),
],
),
Row(
children: [
const Text('增长率: ', style: TextStyle(color: Colors.white70)),
Expanded(
child: Slider(
value: _growthRate,
min: 0.1,
max: 0.5,
onChanged: (v) => setState(() => _growthRate = v),
),
),
Text(_growthRate.toStringAsFixed(3),
style: const TextStyle(color: Colors.amber)),
],
),
],
),
);
}
}
class GoldenSpiralPainter extends CustomPainter {
final double time;
final int turns;
final double growthRate;
GoldenSpiralPainter(this.time, this.turns, this.growthRate);
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final maxR = min(size.width, size.height) / 2 – 40;
canvas.drawRect(
Rect.fromLTWH(0, 0, size.width, size.height),
Paint()..color = const Color(0xFF0a0a15),
);
_drawGrid(canvas, center, maxR);
_drawGoldenSpiral(canvas, center, maxR);
_drawParticles(canvas, center, maxR);
canvas.drawCircle(center, 4, Paint()..color = Colors.amber);
}
void _drawGrid(Canvas canvas, Offset center, double maxR) {
final gridPaint = Paint()
..color = Colors.white10
..style = PaintingStyle.stroke;
for (int r = 20; r <= maxR; r += 40) {
canvas.drawCircle(center, r.toDouble(), gridPaint);
}
for (int i = 0; i < 12; i++) {
final angle = i * pi / 6 + time * 0.1;
canvas.drawLine(
center,
Offset(center.dx + maxR * cos(angle), center.dy + maxR * sin(angle)),
gridPaint,
);
}
}
void _drawGoldenSpiral(Canvas canvas, Offset center, double maxR) {
final path = Path();
final spiralPaint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 2;
for (double theta = 0; theta <= turns * 2 * pi; theta += 0.02) {
final r = maxR * 0.1 * exp(growthRate * theta);
if (r > maxR) break;
final x = center.dx + r * cos(theta + time);
final y = center.dy + r * sin(theta + time);
if (theta == 0) {
path.moveTo(x, y);
} else {
path.lineTo(x, y);
}
}
spiralPaint.shader = LinearGradient(
colors: [Colors.amber, Colors.orange, Colors.red],
).createShader(Rect.fromCircle(center: center, radius: maxR));
canvas.drawPath(path, spiralPaint);
final mirrorPath = Path();
for (double theta = 0; theta <= turns * 2 * pi; theta += 0.02) {
final r = maxR * 0.1 * exp(growthRate * theta);
if (r > maxR) break;
final x = center.dx + r * cos(–theta + time);
final y = center.dy + r * sin(–theta + time);
if (theta == 0) {
mirrorPath.moveTo(x, y);
} else {
mirrorPath.lineTo(x, y);
}
}
spiralPaint.shader = LinearGradient(
colors: [Colors.purple, Colors.indigo, Colors.blue],
).createShader(Rect.fromCircle(center: center, radius: maxR));
canvas.drawPath(mirrorPath, spiralPaint);
}
void _drawParticles(Canvas canvas, Offset center, double maxR) {
for (int i = 0; i < 30; i++) {
final theta = i * goldenAngle + time * 0.5;
final r = maxR * 0.8 * sqrt(i / 30);
final x = center.dx + r * cos(theta);
final y = center.dy + r * sin(theta);
final hue = (i * 12 + time * 30) % 360;
canvas.drawCircle(
Offset(x, y),
3 + 2 * sin(time * 2 + i),
Paint()..color = HSVColor.fromAHSV(1, hue, 0.8, 1).toColor(),
);
}
}
bool shouldRepaint(covariant GoldenSpiralPainter old) => true;
}
class SpiralFamilyDemo extends StatefulWidget {
const SpiralFamilyDemo({super.key});
State<SpiralFamilyDemo> createState() => _SpiralFamilyDemoState();
}
class _SpiralFamilyDemoState extends State<SpiralFamilyDemo>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
double _time = 0;
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 16),
)..repeat();
_controller.addListener(() {
_time += 0.016;
setState(() {});
});
}
void dispose() {
_controller.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('螺旋家族')),
body: CustomPaint(
painter: SpiralFamilyPainter(_time),
size: Size.infinite,
),
);
}
}
class SpiralFamilyPainter extends CustomPainter {
final double time;
SpiralFamilyPainter(this.time);
void paint(Canvas canvas, Size size) {
canvas.drawRect(
Rect.fromLTWH(0, 0, size.width, size.height),
Paint()..color = const Color(0xFF0a0a15),
);
final positions = [
Offset(size.width * 0.25, size.height * 0.25),
Offset(size.width * 0.75, size.height * 0.25),
Offset(size.width * 0.25, size.height * 0.75),
Offset(size.width * 0.75, size.height * 0.75),
];
final labels = ['阿基米德', '黄金螺旋', '费马螺旋', '双曲螺旋'];
final colors = [Colors.amber, Colors.purple, Colors.teal, Colors.orange];
for (int i = 0; i < 4; i++) {
_drawSpiral(canvas, positions[i], i, colors[i], labels[i]);
}
}
void _drawSpiral(Canvas canvas, Offset center, int type, Color color, String label) {
final maxR = 60.0;
final path = Path();
for (double theta = 0; theta <= 6 * pi; theta += 0.05) {
double r;
switch (type) {
case 0:
r = 5 + theta * 8;
break;
case 1:
r = 5 * exp(0.1 * theta);
break;
case 2:
r = 15 * sqrt(theta);
break;
case 3:
r = theta > 0.1 ? 150 / theta : 150;
break;
default:
r = theta;
}
if (r > maxR) break;
final x = center.dx + r * cos(theta + time);
final y = center.dy + r * sin(theta + time);
if (theta == 0) {
path.moveTo(x, y);
} else {
path.lineTo(x, y);
}
}
canvas.drawPath(
path,
Paint()
..color = color
..style = PaintingStyle.stroke
..strokeWidth = 2,
);
final textPainter = TextPainter(
text: TextSpan(
text: label,
style: TextStyle(color: color, fontSize: 12),
),
textDirection: TextDirection.ltr,
)..layout();
textPainter.paint(canvas, Offset(center.dx – 30, center.dy + maxR + 10));
}
bool shouldRepaint(covariant SpiralFamilyPainter old) => true;
}
class SunflowerDemo extends StatefulWidget {
const SunflowerDemo({super.key});
State<SunflowerDemo> createState() => _SunflowerDemoState();
}
class _SunflowerDemoState extends State<SunflowerDemo>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
double _time = 0;
int _seedCount = 200;
bool _animate = true;
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 16),
)..repeat();
_controller.addListener(() {
if (_animate) {
_time += 0.016;
setState(() {});
}
});
}
void dispose() {
_controller.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('向日葵种子排列')),
body: Column(
children: [
Expanded(
child: CustomPaint(
painter: SunflowerPainter(_time, _seedCount),
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: _seedCount.toDouble(),
min: 50,
max: 500,
divisions: 45,
onChanged: (v) => setState(() => _seedCount = v.toInt()),
),
),
Text('$_seedCount', style: const TextStyle(color: Colors.orange)),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => setState(() => _animate = !_animate),
child: Text(_animate ? '暂停' : '播放'),
),
const SizedBox(width: 16),
ElevatedButton(
onPressed: () => setState(() => _time = 0),
child: const Text('重置'),
),
],
),
],
),
);
}
}
class SunflowerPainter extends CustomPainter {
final double time;
final int seedCount;
SunflowerPainter(this.time, this.seedCount);
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final maxR = min(size.width, size.height) / 2 – 40;
canvas.drawRect(
Rect.fromLTWH(0, 0, size.width, size.height),
Paint()..color = const Color(0xFF0a0a15),
);
canvas.drawCircle(
center,
maxR,
Paint()..color = Colors.brown.withOpacity(0.1),
);
for (int i = 0; i < seedCount; i++) {
final theta = i * goldenAngle + time * 0.2;
final r = maxR * sqrt(i / seedCount);
final x = center.dx + r * cos(theta);
final y = center.dy + r * sin(theta);
final t = i / seedCount;
final hue = 30 + t * 30;
final saturation = 0.8 – t * 0.3;
final value = 0.6 + t * 0.4;
final seedSize = 4 + 4 * (1 – t) + sin(time * 3 + i * 0.1) * 1;
canvas.drawCircle(
Offset(x, y),
seedSize,
Paint()..color = HSVColor.fromAHSV(1, hue, saturation, value).toColor(),
);
}
_drawFibonacciSpirals(canvas, center, maxR);
}
void _drawFibonacciSpirals(Canvas canvas, Offset center, double maxR) {
final fibNumbers = [8, 13, 21, 34];
final colors = [Colors.red, Colors.green, Colors.blue, Colors.purple];
for (int f = 0; f < fibNumbers.length; f++) {
final n = fibNumbers[f];
final paint = Paint()
..color = colors[f].withOpacity(0.3)
..style = PaintingStyle.stroke
..strokeWidth = 1;
for (int start = 0; start < n; start++) {
final path = Path();
for (int i = start; i < seedCount; i += n) {
final theta = i * goldenAngle;
final r = maxR * sqrt(i / seedCount);
final x = center.dx + r * cos(theta);
final y = center.dy + r * sin(theta);
if (i == start) {
path.moveTo(x, y);
} else {
path.lineTo(x, y);
}
}
canvas.drawPath(path, paint);
}
}
}
bool shouldRepaint(covariant SunflowerPainter old) => true;
}
class GoldenRectangleDemo extends StatefulWidget {
const GoldenRectangleDemo({super.key});
State<GoldenRectangleDemo> createState() => _GoldenRectangleDemoState();
}
class _GoldenRectangleDemoState extends State<GoldenRectangleDemo>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
double _time = 0;
int _depth = 8;
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 16),
)..repeat();
_controller.addListener(() {
_time += 0.016;
setState(() {});
});
}
void dispose() {
_controller.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('黄金矩形')),
body: Column(
children: [
Expanded(
child: CustomPaint(
painter: GoldenRectanglePainter(_time, _depth),
size: Size.infinite,
),
),
Container(
padding: const EdgeInsets.all(16),
child: Row(
children: [
const Text('递归深度: '),
Expanded(
child: Slider(
value: _depth.toDouble(),
min: 2,
max: 12,
divisions: 10,
onChanged: (v) => setState(() => _depth = v.toInt()),
),
),
Text('$_depth'),
],
),
),
],
),
);
}
}
class GoldenRectanglePainter extends CustomPainter {
final double time;
final int depth;
GoldenRectanglePainter(this.time, this.depth);
void paint(Canvas canvas, Size size) {
canvas.drawRect(
Rect.fromLTWH(0, 0, size.width, size.height),
Paint()..color = const Color(0xFF0a0a15),
);
final center = Offset(size.width / 2, size.height / 2);
final rectWidth = min(size.width, size.height) * 0.8;
final rectHeight = rectWidth / phi;
_drawGoldenRectangles(
canvas,
center.dx – rectWidth / 2,
center.dy – rectHeight / 2,
rectWidth,
rectHeight,
0,
);
_drawGoldenSpiralInRectangles(canvas, center, rectWidth, rectHeight);
}
void _drawGoldenRectangles(
Canvas canvas,
double x,
double y,
double width,
double height,
int currentDepth,
) {
if (currentDepth >= depth || width < 2 || height < 2) return;
final hue = (currentDepth * 30 + time * 20) % 360;
final paint = Paint()
..color = HSVColor.fromAHSV(0.3, hue, 0.7, 0.9).toColor()
..style = PaintingStyle.fill;
canvas.drawRect(Rect.fromLTWH(x, y, width, height), paint);
final strokePaint = Paint()
..color = HSVColor.fromAHSV(1, hue, 0.8, 1).toColor()
..style = PaintingStyle.stroke
..strokeWidth = 2;
canvas.drawRect(Rect.fromLTWH(x, y, width, height), strokePaint);
if (width > height) {
_drawGoldenRectangles(
canvas,
x + height,
y,
width – height,
height,
currentDepth + 1,
);
} else {
_drawGoldenRectangles(
canvas,
x,
y + width,
width,
height – width,
currentDepth + 1,
);
}
}
void _drawGoldenSpiralInRectangles(
Canvas canvas,
Offset center,
double width,
double height,
) {
final path = Path();
var x = center.dx – width / 2;
var y = center.dy – height / 2;
var w = width;
var h = height;
for (int i = 0; i < depth; i++) {
final rect = Rect.fromLTWH(x, y, w > h ? h : w, w > h ? h : w);
if (i == 0) {
path.arcTo(rect, –pi / 2, pi / 2, false);
} else {
path.arcTo(rect, path.getBounds().center.dx < rect.center.dx ? 0 : –pi,
w > h ? –pi / 2 : pi / 2, false);
}
if (w > h) {
x += h;
w -= h;
} else {
y += w;
h -= w;
}
if (w < 1 || h < 1) break;
}
canvas.drawPath(
path,
Paint()
..color = Colors.amber
..style = PaintingStyle.stroke
..strokeWidth = 3,
);
}
bool shouldRepaint(covariant GoldenRectanglePainter old) => true;
}
class FibonacciDemo extends StatefulWidget {
const FibonacciDemo({super.key});
State<FibonacciDemo> createState() => _FibonacciDemoState();
}
class _FibonacciDemoState extends State<FibonacciDemo>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
double _time = 0;
int _visibleCount = 1;
final List<int> _fib = [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144];
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 16),
)..repeat();
_controller.addListener(() {
_time += 0.016;
if (_time.toInt() % 60 == 0 && _visibleCount < _fib.length) {
setState(() => _visibleCount++);
}
setState(() {});
});
}
void dispose() {
_controller.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('斐波那契动画')),
body: Column(
children: [
Expanded(
child: CustomPaint(
painter: FibonacciPainter(_time, _visibleCount, _fib),
size: Size.infinite,
),
),
Container(
padding: const EdgeInsets.all(16),
child: Wrap(
spacing: 8,
children: List.generate(
_visibleCount,
(i) => Chip(
label: Text('${_fib[i]}'),
backgroundColor: HSVColor.fromAHSV(1, i * 30.0, 0.7, 0.9).toColor(),
),
),
),
),
],
),
);
}
}
class FibonacciPainter extends CustomPainter {
final double time;
final int visibleCount;
final List<int> fib;
FibonacciPainter(this.time, this.visibleCount, this.fib);
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),
);
var x = center.dx;
var y = center.dy;
var direction = 0;
final scale = min(size.width, size.height) / 300;
for (int i = 0; i < visibleCount && i < fib.length; i++) {
final size = fib[i] * scale;
final hue = (i * 30 + time * 20) % 360;
Offset rectCenter;
switch (direction) {
case 0:
rectCenter = Offset(x + size / 2, y + size / 2);
x += size;
break;
case 1:
rectCenter = Offset(x – size / 2, y + size / 2);
y += size;
break;
case 2:
rectCenter = Offset(x – size / 2, y – size / 2);
x -= size;
break;
default:
rectCenter = Offset(x + size / 2, y – size / 2);
y -= size;
}
canvas.drawRect(
Rect.fromCenter(center: rectCenter, width: size, height: size),
Paint()
..color = HSVColor.fromAHSV(0.3, hue, 0.7, 0.9).toColor()
..style = PaintingStyle.fill,
);
canvas.drawRect(
Rect.fromCenter(center: rectCenter, width: size, height: size),
Paint()
..color = HSVColor.fromAHSV(1, hue, 0.8, 1).toColor()
..style = PaintingStyle.stroke
..strokeWidth = 2,
);
direction = (direction + 1) % 4;
}
}
bool shouldRepaint(covariant FibonacciPainter old) => true;
}
📝 四、数学原理深入解析
📐 4.1 极坐标曲线方程
极坐标系统中有许多经典的数学曲线:
玫瑰曲线(Rose Curves):
r = a × cos(n × θ)
当 n 为奇数:n 个花瓣
当 n 为偶数:2n 个花瓣
示例:
n = 3: 三叶玫瑰 n = 4: 八叶玫瑰
╱╲ ╲╱╲╱
╱ ╲ ╱ ╲
╱ ╲ ╱ ╲
──────── ────────
╲ ╱ ╲ ╱
╲ ╱ ╲ ╱
╲╱ ╲╱
对数螺旋的数学性质:
r = a × e^(b × θ)
等角性质:螺旋线上任意点的切线与径向的夹角恒定
夹角 α 满足:tan(α) = 1/b
自然界应用:
– 鹦鹉螺壳:生长过程中保持形状相似
– 银河系旋臂:恒星分布近似对数螺旋
– 旋风结构:气流旋转轨迹
费马螺旋的等面积性质:
r² = a² × θ
面积公式:A = (1/2) × r² × θ
对于费马螺旋:A = (1/2) × a² × θ²
等面积性质:角度等量增加,扫过的面积也等量增加
应用:向日葵种子排列,每个种子获得相等空间
🔄 4.2 黄金比例的数学性质
代数性质:
φ = (1 + √5) / 2 ≈ 1.618033988749…
基本恒等式:
φ² = φ + 1
φ³ = 2φ + 1
φ⁴ = 3φ + 2
φ⁵ = 5φ + 3
φⁿ = F(n)φ + F(n-1)
其中 F(n) 是第 n 个斐波那契数
共轭:
φ' = (1 – √5) / 2 ≈ -0.618
φ × φ' = -1
φ + φ' = 1
连分数表示:
φ = 1 + 1/(1 + 1/(1 + 1/(1 + …)))
这是所有数中收敛最慢的连分数
因此 φ 被称为"最无理的数"
几何作图:
用尺规作图构造黄金分割点:
1. 作线段 AB
2. 在 B 点作垂线 BC = AB/2
3. 连接 AC
4. 以 C 为圆心,CB 为半径画弧交 AC 于 D
5. 以 A 为圆心,AD 为半径画弧交 AB 于 P
则 P 为 AB 的黄金分割点
AP/AB = φ – 1 ≈ 0.618
🌸 4.3 斐波那契数列的通项公式
比内公式(Binet’s Formula):
F(n) = (φⁿ – (-φ)⁻ⁿ) / √5
这是斐波那契数列的封闭形式解
直接计算第 n 项,无需递归
证明思路:
设 F(n) = F(n-1) + F(n-2)
特征方程:x² = x + 1
解为 φ 和 φ' = -1/φ
通解:F(n) = A·φⁿ + B·φ'ⁿ
由初始条件确定 A = 1/√5, B = -1/√5
斐波那契恒等式:
F(n+m) = F(n)·F(m+1) + F(n-1)·F(m)
F(2n) = F(n)·(F(n+1) + F(n-1))
F(n)² + F(n+1)² = F(2n+1)
F(1) + F(2) + … + F(n) = F(n+2) – 1
F(1)² + F(2)² + … + F(n)² = F(n)·F(n+1)
🎯 4.4 黄金角度的优化原理
为什么是 137.5°:
黄金角度 = 360° × (1 – 1/φ) ≈ 137.507764°
有理逼近:
360° × 1/2 = 180° → 2 条射线
360° × 1/3 = 120° → 3 条射线
360° × 2/5 = 144° → 5 条射线
360° × 3/8 = 135° → 8 条射线
黄金角度是无理数角度
永远不会回到起点
实现最优空间填充
空间填充效率:
对于 n 个元素排列:
有理角度 θ = p/q × 360°:
– 只产生 q 条射线
– 元素沿射线聚集
– 空间利用率低
黄金角度 θ = 137.5°:
– 永不重复
– 均匀分布
– 最大空间利用率
数学证明:
黄金角度的连分数展开全为 1
使得任意有限逼近都是最优的
🔬 五、高级应用场景
🎨 5.1 数据可视化
螺旋布局在数据可视化中的独特优势:
时间序列螺旋图:
将时间数据映射到螺旋:
– 内圈:较早时间
– 外圈:较晚时间
– 相同季节/周期对齐
优势:
– 周期性模式一目了然
– 长期趋势清晰可见
– 节省显示空间
层次数据螺旋:
树状结构的螺旋表示:
– 根节点在中心
– 子节点向外扩展
– 兄弟节点按角度分布
应用场景:
– 文件系统浏览
– 组织架构展示
– 知识图谱可视化
🌐 5.2 生成艺术
螺旋在生成艺术中的创意应用:
参数化螺旋艺术:
// 螺旋艺术生成器
class SpiralArtGenerator {
final int layers;
final double twistFactor;
final ColorScheme colors;
List<Path> generate() {
final paths = <Path>[];
for (int layer = 0; layer < layers; layer++) {
final path = Path();
final phase = layer * 2 * pi / layers;
for (double theta = 0; theta < 10 * pi; theta += 0.01) {
final r = _calculateRadius(theta, layer);
final x = r * cos(theta + phase);
final y = r * sin(theta + phase);
if (theta == 0) path.moveTo(x, y);
else path.lineTo(x, y);
}
paths.add(path);
}
return paths;
}
double _calculateRadius(double theta, int layer) {
// 组合多种螺旋模式
return 50 * exp(0.1 * theta) +
20 * sin(theta * twistFactor + layer);
}
}
黄金螺旋构图:
在摄影和绘画中应用黄金螺旋:
1. 将画面按黄金矩形分割
2. 绘制黄金螺旋曲线
3. 重要元素放置在螺旋线上
4. 视觉焦点位于螺旋中心
效果:
– 自然引导视线
– 和谐平衡的构图
– 符合审美直觉
📱 5.3 鸿蒙多端适配
在鸿蒙系统上实现螺旋可视化的多端适配:
响应式尺寸计算:
class ResponsiveSpiral {
static double calculateRadius(BuildContext context) {
final size = MediaQuery.of(context).size;
final deviceType = _getDeviceType(size);
switch (deviceType) {
case DeviceType.phone:
return min(size.width, size.height) * 0.35;
case DeviceType.tablet:
return min(size.width, size.height) * 0.4;
case DeviceType.tv:
return min(size.width, size.height) * 0.3;
}
}
static int calculateDetailLevel(BuildContext context) {
final performance = DevicePerformance.getLevel();
switch (performance) {
case PerformanceLevel.high:
return 128;
case PerformanceLevel.medium:
return 64;
case PerformanceLevel.low:
return 32;
}
}
}
性能分级渲染:
class AdaptiveSpiralPainter extends CustomPainter {
final int detailLevel;
AdaptiveSpiralPainter(this.detailLevel);
void paint(Canvas canvas, Size size) {
// 根据性能等级调整渲染细节
final stepCount = detailLevel * 4;
final particleCount = detailLevel ~/ 2;
// 绘制螺旋
_drawSpiral(canvas, size, stepCount);
// 绘制粒子(高性能设备)
if (detailLevel > 64) {
_drawParticles(canvas, size, particleCount);
}
}
}
📊 六、性能优化策略
⚡ 6.1 渲染优化
螺旋渲染的性能优化技巧:
预计算路径:
class CachedSpiralPath {
Path? _cachedPath;
int _lastTurns = 0;
double _lastGrowthRate = 0;
Path getPath(int turns, double growthRate, Offset center, double maxR) {
if (_cachedPath != null &&
_lastTurns == turns &&
_lastGrowthRate == growthRate) {
return _cachedPath!;
}
_cachedPath = _computePath(turns, growthRate, center, maxR);
_lastTurns = turns;
_lastGrowthRate = growthRate;
return _cachedPath!;
}
Path _computePath(int turns, double growthRate, Offset center, double maxR) {
final path = Path();
for (double theta = 0; theta <= turns * 2 * pi; theta += 0.02) {
final r = maxR * 0.1 * exp(growthRate * theta);
if (r > maxR) break;
final x = center.dx + r * cos(theta);
final y = center.dy + r * sin(theta);
if (theta == 0) path.moveTo(x, y);
else path.lineTo(x, y);
}
return path;
}
}
使用 Isolate 计算:
Future<List<Offset>> computeSunflowerSeeds(SunflowerParams params) async {
return await compute(_generateSeeds, params);
}
List<Offset> _generateSeeds(SunflowerParams params) {
final seeds = <Offset>[];
for (int i = 0; i < params.count; i++) {
final theta = i * goldenAngle;
final r = params.maxRadius * sqrt(i / params.count);
seeds.add(Offset(
params.center.dx + r * cos(theta),
params.center.dy + r * sin(theta),
));
}
return seeds;
}
💾 6.2 内存优化
对象池模式:
class SpiralPointPool {
static final SpiralPointPool _instance = SpiralPointPool._();
final List<List<Offset>> _pools = [];
factory SpiralPointPool() => _instance;
SpiralPointPool._();
List<Offset> acquire(int size) {
for (var pool in _pools) {
if (pool.length >= size) {
_pools.remove(pool);
return pool;
}
}
return List.generate(size, (i) => Offset.zero);
}
void release(List<Offset> pool) {
if (_pools.length < 10) {
_pools.add(pool);
}
}
}
懒加载策略:
class LazySpiralData {
List<Offset>? _points;
final int Function() _pointCount;
final Offset Function(int) _pointGenerator;
LazySpiralData(this._pointCount, this._pointGenerator);
List<Offset> get points {
_points ??= List.generate(
_pointCount(),
(i) => _pointGenerator(i),
);
return _points!;
}
Offset operator [](int index) => points[index];
}
🎓 七、学习资源与拓展
📚 推荐阅读
| 黄金比例 | 《神圣比例》- 卢卡·帕乔利 | ⭐⭐ |
| 斐波那契数列 | 《斐波那契数列》- 托马斯·科斯齐 | ⭐⭐ |
| 螺旋几何 | 《螺旋:从自然到艺术》 | ⭐⭐ |
| 分形几何 | 《大自然的分形几何学》 | ⭐⭐⭐ |
| 极坐标变换 | 《高等数学》- 极坐标章节 | ⭐⭐ |
🔗 相关项目
- D3.js 螺旋图:数据可视化的螺旋布局
- Processing 螺旋艺术:创意编程示例
- Wolfram MathWorld:数学公式参考
- Desmos 图形计算器:交互式探索
🎯 练习建议
📝 八、总结
本篇文章深入探讨了螺旋与黄金分割的数学原理及其在 Flutter 中的可视化实现。
✅ 核心知识点回顾
| 📐 黄金比例 | φ ≈ 1.618,自然界最优美的比例 |
| 🔢 斐波那契数列 | 相邻项比值趋近黄金比例 |
| 🌀 螺旋曲线 | 阿基米德、对数、费马等类型 |
| 🌻 黄金角度 | 137.5°,最优空间排列角度 |
| 📦 黄金矩形 | 递归分割构造黄金螺旋 |
| ⚡ 性能优化 | 预计算、对象池、懒加载 |
⭐ 最佳实践要点
- ✅ 使用极坐标简化螺旋计算
- ✅ 黄金角度实现均匀分布
- ✅ 预计算路径提升性能
- ✅ 响应式适配多端设备
🚀 进阶方向
- 🔮 3D 螺旋空间可视化
- ✨ 音频驱动的螺旋动画
- 🎨 参数化螺旋艺术生成
- 📊 螺旋布局数据可视化
💡 提示:本文代码基于 Flutter for Harmony 开发,可在鸿蒙设备上流畅运行。
网硕互联帮助中心






评论前必须登录!
注册