Flutter for Harmony 跨平台开发实战:超形状与超椭圆——参数方程的形态边界
超椭圆(Superellipse)是一种介于椭圆和矩形之间的曲线,由丹麦数学家皮特·海因(Piet Hein)在1959年发现。它的数学形式简洁优雅,在建筑和设计领域有广泛应用。超椭圆方程:不同 n 值的形态:📐 1.2 Gielis 超形状公式2003年,比利时植物学家 Johan Gielis 提出了更通用的超形状公式,可以描述从星形到花瓣的各种自然形态。Gielis 超形状公式:参数影响:
·

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
🌸 一、超形状:数学之美
📚 1.1 超椭圆的发现
超椭圆(Superellipse)是一种介于椭圆和矩形之间的曲线,由丹麦数学家皮特·海因(Piet Hein)在1959年发现。它的数学形式简洁优雅,在建筑和设计领域有广泛应用。
超椭圆方程:
标准形式:
|x/a|^n + |y/b|^n = 1
当 n = 2 时:标准椭圆
当 n > 2 时:趋向矩形
当 n < 2 时:趋向星形
当 n → ∞:矩形
当 n → 0:十字形
参数方程:
x = a·cos^(2/n)(t)
y = b·sin^(2/n)(t)
其中 t ∈ [0, 2π]
不同 n 值的形态:
n = 0.5: n = 1: n = 2: n = 4: n → ∞:
╲ ╲ ──── ┌──┐ ┌──┐
╲ ╲ ╱ ╲ │ │ │ │
╲ ╲ ╱ ╲ │ │ │ │
● ● ● ● │ │ │ │
╱ ╱ ╲ ╱ │ │ │ │
╱ ╱ ╲ ╱ │ │ │ │
╱ ╱ ──── └──┘ └──┘
十字形 菱形 椭圆 超椭圆 矩形
📐 1.2 Gielis 超形状公式
2003年,比利时植物学家 Johan Gielis 提出了更通用的超形状公式,可以描述从星形到花瓣的各种自然形态。
Gielis 超形状公式:
极坐标形式:
r(θ) = (|cos(mθ/4)/a|^(n₂) + |sin(mθ/4)/b|^(n₃))^(-1/n₁)
参数说明:
- m:对称轴数量
- a, b:缩放因子
- n₁, n₂, n₃:形状指数
特殊情形:
- m = 4, n₁ = n₂ = n₃ = 2:椭圆
- m = 4, n₁ = n₂ = n₃ → ∞:矩形
- m = 5, n₁ = n₂ = n₃ = 1:五角星
- m = 6, n₁ = n₂ = n₃ = 1:六角星
参数影响:
| 参数 | 作用 | 效果 |
|---|---|---|
| m | 对称轴数 | 决定花瓣/尖角数量 |
| n₁ | 整体曲率 | 控制凹陷程度 |
| n₂ | x 方向影响 | 调整水平形态 |
| n₃ | y 方向影响 | 调整垂直形态 |
| a, b | 缩放比例 | 控制各向异性 |
🔬 1.3 自然界中的超形状
超形状公式可以精确描述许多自然形态:
植物形态:
花朵:
- m = 5, n₁ = 1, n₂ = n₃ = 1:五瓣花
- m = 6, n₁ = 0.3, n₂ = n₃ = 1:六瓣花
- m = 8, n₁ = 0.5, n₂ = n₃ = 1:八瓣花
叶子:
- m = 1, n₁ = 0.5, n₂ = n₃ = 1:心形叶
- m = 3, n₁ = 1, n₂ = n₃ = 1:三叶草
果实:
- m = 4, n₁ = 4, n₂ = n₃ = 4:近似方形番茄
动物形态:
海星:m = 5, n₁ ≈ 0.5
贝壳:m = 1, n₁ < 1(螺旋形态)
蝴蝶翅膀:m = 2, n₁ ≈ 0.3
超形状示意:
m = 3 (三角): m = 4 (四角): m = 5 (五角): m = 6 (六角):
△ ◇ ★ ⬡
╱ ╲ ╱╲ ╱│╲ ╱ ╲
╱ ╲ ╱ ╲ ╱ │ ╲ ╱ ╲
●─────● ●────● ●───●───● ●─────●
╲ ╱ ╲ ╱ ╲ │ ╱ ╲ ╱
╲ ╱ ╲╱ ╲│╱ ╲ ╱
▽ ◇ ★ ⬡
n₁ 越小,凹陷越深
🎯 1.4 超形状的数学性质
连续性与可微性:
当 n₁, n₂, n₃ > 0 时,超形状是连续的
可微性:
- n₁, n₂, n₃ ≥ 1:曲线光滑
- n₁, n₂, n₃ < 1:存在尖点
- n₁, n₂, n₃ = 1:临界情况
曲率:
κ = (r² + 2r'² - rr'') / (r² + r'²)^(3/2)
在尖点处曲率趋于无穷
面积计算:
超形状围成的面积:
A = ∫₀^(2π) ½r²(θ)dθ
对于简单情况(n₁ = n₂ = n₃):
A = 4ab · Γ(1 + 1/n)² / Γ(1 + 2/n)
其中 Γ 是伽马函数
特殊情况:
- n = 2(椭圆):A = πab
- n → ∞(矩形):A = 4ab
周长计算:
周长公式:
L = ∫₀^(2π) √(r² + (dr/dθ)²)dθ
对于超椭圆,没有简单的解析解
通常使用数值积分计算
🔧 二、超形状的 Dart 实现
🧮 2.1 超形状生成器
import 'dart:math';
/// 超形状参数
class SuperShapeParams {
final double m;
final double n1;
final double n2;
final double n3;
final double a;
final double b;
const SuperShapeParams({
required this.m,
required this.n1,
required this.n2,
required this.n3,
this.a = 1.0,
this.b = 1.0,
});
factory SuperShapeParams.ellipse({double a = 1, double b = 1}) =>
SuperShapeParams(m: 4, n1: 2, n2: 2, n3: 2, a: a, b: b);
factory SuperShapeParams.star(int points, {double depth = 0.5}) =>
SuperShapeParams(m: points.toDouble(), n1: depth, n2: 1, n3: 1);
factory SuperShapeParams.flower(int petals, {double curvature = 0.3}) =>
SuperShapeParams(m: petals.toDouble(), n1: curvature, n2: 1, n3: 1);
factory SuperShapeParams.rectangle() =>
const SuperShapeParams(m: 4, n1: 100, n2: 100, n3: 100);
SuperShapeParams copyWith({
double? m,
double? n1,
double? n2,
double? n3,
double? a,
double? b,
}) {
return SuperShapeParams(
m: m ?? this.m,
n1: n1 ?? this.n1,
n2: n2 ?? this.n2,
n3: n3 ?? this.n3,
a: a ?? this.a,
b: b ?? this.b,
);
}
}
/// 超形状生成器
class SuperShapeGenerator {
final SuperShapeParams params;
SuperShapeGenerator(this.params);
double radius(double theta) {
final m = params.m;
final n1 = params.n1;
final n2 = params.n2;
final n3 = params.n3;
final a = params.a;
final b = params.b;
final term1 = pow(_absCos(m * theta / 4) / a, n2);
final term2 = pow(_absSin(m * theta / 4) / b, n3);
if (term1 + term2 == 0) return 0;
return pow(term1 + term2, -1 / n1);
}
double _absCos(double x) => cos(x).abs();
double _absSin(double x) => sin(x).abs();
List<Offset> generatePoints({
required int pointCount,
double scale = 1.0,
Offset center = Offset.zero,
}) {
final points = <Offset>[];
final dTheta = 2 * pi / pointCount;
for (int i = 0; i < pointCount; i++) {
final theta = i * dTheta;
final r = radius(theta) * scale;
final x = center.dx + r * cos(theta);
final y = center.dy + r * sin(theta);
points.add(Offset(x, y));
}
return points;
}
Path toPath({
required int pointCount,
double scale = 1.0,
Offset center = Offset.zero,
}) {
final points = generatePoints(
pointCount: pointCount,
scale: scale,
center: center,
);
final path = Path();
if (points.isEmpty) return path;
path.moveTo(points[0].dx, points[0].dy);
for (int i = 1; i < points.length; i++) {
path.lineTo(points[i].dx, points[i].dy);
}
path.close();
return path;
}
double calculateArea(double scale) {
final pointCount = 1000;
final dTheta = 2 * pi / pointCount;
double area = 0;
for (int i = 0; i < pointCount; i++) {
final theta = i * dTheta;
final r = radius(theta) * scale;
area += 0.5 * r * r * dTheta;
}
return area;
}
double calculatePerimeter(double scale) {
final pointCount = 1000;
final dTheta = 2 * pi / pointCount;
double perimeter = 0;
Offset? prevPoint;
for (int i = 0; i <= pointCount; i++) {
final theta = i * dTheta;
final r = radius(theta) * scale;
final x = r * cos(theta);
final y = r * sin(theta);
final point = Offset(x, y);
if (prevPoint != null) {
final dx = point.dx - prevPoint.dx;
final dy = point.dy - prevPoint.dy;
perimeter += sqrt(dx * dx + dy * dy);
}
prevPoint = point;
}
return perimeter;
}
}
⚡ 2.2 超椭圆专用类
/// 超椭圆类
class SuperEllipse {
final double a;
final double b;
final double n;
SuperEllipse({required this.a, required this.b, required this.n});
factory SuperEllipse.circle(double radius) =>
SuperEllipse(a: radius, b: radius, n: 2);
factory SuperEllipse.ellipse(double a, double b) =>
SuperEllipse(a: a, b: b, n: 2);
factory SuperEllipse.rectangle(double width, double height) =>
SuperEllipse(a: width / 2, b: height / 2, n: 100);
double x(double t) => a * _sign(cos(t)) * pow(cos(t).abs(), 2 / n);
double y(double t) => b * _sign(sin(t)) * pow(sin(t).abs(), 2 / n);
double _sign(double x) {
if (x > 0) return 1;
if (x < 0) return -1;
return 0;
}
List<Offset> generatePoints(int pointCount) {
return List.generate(pointCount, (i) {
final t = 2 * pi * i / pointCount;
return Offset(x(t), y(t));
});
}
Path toPath(int pointCount) {
final points = generatePoints(pointCount);
final path = Path();
if (points.isEmpty) return path;
path.moveTo(points[0].dx, points[0].dy);
for (int i = 1; i < points.length; i++) {
path.lineTo(points[i].dx, points[i].dy);
}
path.close();
return path;
}
double get area {
if (n == 2) return pi * a * b;
final gamma1 = _gamma(1 + 1 / n);
final gamma2 = _gamma(1 + 2 / n);
return 4 * a * b * gamma1 * gamma1 / gamma2;
}
double _gamma(double x) {
if (x == 1) return 1;
if (x == 0.5) return sqrt(pi);
return (x - 1) * _gamma(x - 1);
}
double curvatureAt(double t) {
if (n == 2) return a * b / pow(a * a * sin(t) * sin(t) + b * b * cos(t) * cos(t), 1.5);
final dx = -2 * a / n * _sign(cos(t)) * pow(cos(t).abs(), 2 / n - 1) * sin(t);
final dy = 2 * b / n * _sign(sin(t)) * pow(sin(t).abs(), 2 / n - 1) * cos(t);
final ddx = -2 * a / n * (pow(cos(t).abs(), 2 / n - 1) * cos(t) +
(2 / n - 1) * _sign(cos(t)) * pow(cos(t).abs(), 2 / n - 2) * (-sin(t)) * sin(t));
final ddy = 2 * b / n * (-pow(sin(t).abs(), 2 / n - 1) * sin(t) +
(2 / n - 1) * _sign(sin(t)) * pow(sin(t).abs(), 2 / n - 2) * cos(t) * cos(t));
return (dx * ddy - dy * ddx).abs() / pow(dx * dx + dy * dy, 1.5);
}
}
🎨 2.3 形态动画控制器
import 'package:flutter/material.dart';
/// 超形状动画控制器
class SuperShapeAnimationController extends ChangeNotifier {
SuperShapeParams _params;
double _rotation = 0;
double _scale = 1.0;
double _morphProgress = 0;
SuperShapeParams? _targetParams;
SuperShapeAnimationController({
SuperShapeParams? params,
}) : _params = params ?? SuperShapeParams.flower(5);
SuperShapeParams get params => _params;
double get rotation => _rotation;
double get scale => _scale;
double get morphProgress => _morphProgress;
void setParams(SuperShapeParams newParams) {
_params = newParams;
notifyListeners();
}
void setM(double value) {
_params = _params.copyWith(m: value);
notifyListeners();
}
void setN1(double value) {
_params = _params.copyWith(n1: value);
notifyListeners();
}
void setN2(double value) {
_params = _params.copyWith(n2: value);
notifyListeners();
}
void setN3(double value) {
_params = _params.copyWith(n3: value);
notifyListeners();
}
void setRotation(double value) {
_rotation = value;
notifyListeners();
}
void setScale(double value) {
_scale = value.clamp(0.1, 5.0);
notifyListeners();
}
void startMorph(SuperShapeParams target) {
_targetParams = target;
_morphProgress = 0;
notifyListeners();
}
void updateMorph(double dt) {
if (_targetParams == null) return;
_morphProgress += dt * 0.5;
if (_morphProgress >= 1.0) {
_params = _targetParams!;
_targetParams = null;
_morphProgress = 0;
} else {
_params = SuperShapeParams(
m: _lerp(_params.m, _targetParams!.m, _morphProgress),
n1: _lerp(_params.n1, _targetParams!.n1, _morphProgress),
n2: _lerp(_params.n2, _targetParams!.n2, _morphProgress),
n3: _lerp(_params.n3, _targetParams!.n3, _morphProgress),
a: _lerp(_params.a, _targetParams!.a, _morphProgress),
b: _lerp(_params.b, _targetParams!.b, _morphProgress),
);
}
notifyListeners();
}
double _lerp(double a, double b, double t) => a + (b - a) * t;
void reset() {
_params = SuperShapeParams.flower(5);
_rotation = 0;
_scale = 1.0;
_morphProgress = 0;
_targetParams = null;
notifyListeners();
}
}
📦 三、完整示例代码
以下是完整的超形状可视化示例代码:
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(const SuperShapeApp());
}
class SuperShapeApp extends StatelessWidget {
const SuperShapeApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: '超形状',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.pink, brightness: Brightness.dark),
useMaterial3: true,
),
home: const SuperShapeHomePage(),
debugShowCheckedModeBanner: false,
);
}
}
class SuperShapeHomePage extends StatelessWidget {
const SuperShapeHomePage({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.tune, color: Colors.pink,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const ParamExplorerDemo()))),
_buildCard(context, title: '预设形状', description: '常见超形状展示', icon: Icons.category, color: Colors.purple,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const PresetShapesDemo()))),
_buildCard(context, title: '形态变换', description: '形状之间的平滑过渡', icon: Icons.transform, color: Colors.teal,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const MorphDemo()))),
_buildCard(context, title: '自然形态', description: '模拟自然界的形状', icon: Icons.nature, color: Colors.green,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const NaturalShapesDemo()))),
],
),
);
}
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 SuperShape {
double m, n1, n2, n3, a, b;
SuperShape({required this.m, required this.n1, required this.n2, required this.n3, this.a = 1, this.b = 1});
double radius(double theta) {
final term1 = pow(cos(m * theta / 4).abs() / a, n2);
final term2 = pow(sin(m * theta / 4).abs() / b, n3);
if (term1 + term2 == 0) return 0;
return pow(term1 + term2, -1 / n1).toDouble();
}
List<Offset> points(int count, double scale, Offset center) {
return List.generate(count, (i) {
final theta = 2 * pi * i / count;
final r = radius(theta) * scale;
return Offset(center.dx + r * cos(theta), center.dy + r * sin(theta));
});
}
SuperShape copyWith({double? m, double? n1, double? n2, double? n3, double? a, double? b}) =>
SuperShape(m: m ?? this.m, n1: n1 ?? this.n1, n2: n2 ?? this.n2, n3: n3 ?? this.n3, a: a ?? this.a, b: b ?? this.b);
}
/// 参数探索演示
class ParamExplorerDemo extends StatefulWidget {
const ParamExplorerDemo({super.key});
State<ParamExplorerDemo> createState() => _ParamExplorerDemoState();
}
class _ParamExplorerDemoState extends State<ParamExplorerDemo> {
double _m = 5, _n1 = 1, _n2 = 1, _n3 = 1;
double _rotation = 0;
final List<Color> _colors = [Colors.pink, Colors.purple, Colors.blue, Colors.cyan, Colors.teal, Colors.green];
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('参数探索')),
body: Column(children: [
Expanded(child: CustomPaint(painter: SuperShapePainter(SuperShape(m: _m, n1: _n1, n2: _n2, n3: _n3), _rotation, _colors), 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('m (对称轴): ', style: TextStyle(color: Colors.white70)),
Expanded(child: Slider(value: _m, min: 1, max: 12, onChanged: (v) => setState(() => _m = v))),
Text(_m.toStringAsFixed(1), style: const TextStyle(color: Colors.pink)),
]),
Row(children: [
const Text('n₁ (曲率): ', style: TextStyle(color: Colors.white70)),
Expanded(child: Slider(value: _n1, min: 0.1, max: 5, onChanged: (v) => setState(() => _n1 = v))),
Text(_n1.toStringAsFixed(2), style: const TextStyle(color: Colors.pink)),
]),
Row(children: [
const Text('n₂ (x影响): ', style: TextStyle(color: Colors.white70)),
Expanded(child: Slider(value: _n2, min: 0.1, max: 5, onChanged: (v) => setState(() => _n2 = v))),
Text(_n2.toStringAsFixed(2), style: const TextStyle(color: Colors.pink)),
]),
Row(children: [
const Text('n₃ (y影响): ', style: TextStyle(color: Colors.white70)),
Expanded(child: Slider(value: _n3, min: 0.1, max: 5, onChanged: (v) => setState(() => _n3 = v))),
Text(_n3.toStringAsFixed(2), style: const TextStyle(color: Colors.pink)),
]),
Row(children: [
const Text('旋转: ', style: TextStyle(color: Colors.white70)),
Expanded(child: Slider(value: _rotation, min: 0, max: 2 * pi, onChanged: (v) => setState(() => _rotation = v))),
Text('${(_rotation * 180 / pi).toStringAsFixed(0)}°', style: const TextStyle(color: Colors.pink)),
]),
]),
);
}
}
class SuperShapePainter extends CustomPainter {
final SuperShape shape;
final double rotation;
final List<Color> colors;
SuperShapePainter(this.shape, this.rotation, this.colors);
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final scale = min(size.width, size.height) * 0.4;
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
for (int layer = 5; layer >= 0; layer--) {
final layerScale = scale * (1 - layer * 0.1);
final layerRotation = rotation + layer * 0.1;
final points = shape.points(360, layerScale, center);
final path = Path();
for (int i = 0; i < points.length; i++) {
final rotatedX = center.dx + (points[i].dx - center.dx) * cos(layerRotation) - (points[i].dy - center.dy) * sin(layerRotation);
final rotatedY = center.dy + (points[i].dx - center.dx) * sin(layerRotation) + (points[i].dy - center.dy) * cos(layerRotation);
if (i == 0) path.moveTo(rotatedX, rotatedY);
else path.lineTo(rotatedX, rotatedY);
}
path.close();
final paint = Paint()
..color = colors[layer % colors.length].withOpacity(0.3 + layer * 0.1)
..style = PaintingStyle.fill;
canvas.drawPath(path, paint);
final strokePaint = Paint()
..color = colors[layer % colors.length]
..style = PaintingStyle.stroke
..strokeWidth = 2;
canvas.drawPath(path, strokePaint);
}
}
bool shouldRepaint(covariant SuperShapePainter old) => true;
}
/// 预设形状演示
class PresetShapesDemo extends StatelessWidget {
const PresetShapesDemo({super.key});
Widget build(BuildContext context) {
final presets = [
('圆形', SuperShape(m: 4, n1: 2, n2: 2, n3: 2)),
('椭圆', SuperShape(m: 4, n1: 2, n2: 2, n3: 2, a: 1.5, b: 1)),
('超椭圆', SuperShape(m: 4, n1: 4, n2: 4, n3: 4)),
('矩形', SuperShape(m: 4, n1: 100, n2: 100, n3: 100)),
('五角星', SuperShape(m: 5, n1: 0.5, n2: 1, n3: 1)),
('六角星', SuperShape(m: 6, n1: 0.5, n2: 1, n3: 1)),
('花瓣', SuperShape(m: 5, n1: 0.3, n2: 1, n3: 1)),
('十字', SuperShape(m: 4, n1: 0.3, n2: 1, n3: 1)),
];
return Scaffold(
appBar: AppBar(title: const Text('预设形状')),
body: GridView.builder(
padding: const EdgeInsets.all(8),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, crossAxisSpacing: 8, mainAxisSpacing: 8),
itemCount: presets.length,
itemBuilder: (ctx, i) {
final (name, shape) = presets[i];
return Card(
child: Column(children: [
Expanded(child: CustomPaint(painter: PresetPainter(shape), size: Size.infinite)),
Padding(padding: const EdgeInsets.all(8), child: Text(name, style: const TextStyle(fontWeight: FontWeight.bold))),
]),
);
},
),
);
}
}
class PresetPainter extends CustomPainter {
final SuperShape shape;
PresetPainter(this.shape);
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final scale = min(size.width, size.height) * 0.35;
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
final points = shape.points(360, scale, center);
final path = Path();
for (int i = 0; i < points.length; i++) {
if (i == 0) path.moveTo(points[i].dx, points[i].dy);
else path.lineTo(points[i].dx, points[i].dy);
}
path.close();
final hue = shape.m * 30;
canvas.drawPath(path, Paint()..color = HSVColor.fromAHSV(0.3, hue, 0.7, 0.9).toColor()..style = PaintingStyle.fill);
canvas.drawPath(path, Paint()..color = HSVColor.fromAHSV(1, hue, 0.8, 1).toColor()..style = PaintingStyle.stroke..strokeWidth = 2);
}
bool shouldRepaint(covariant PresetPainter old) => false;
}
/// 形态变换演示
class MorphDemo extends StatefulWidget {
const MorphDemo({super.key});
State<MorphDemo> createState() => _MorphDemoState();
}
class _MorphDemoState extends State<MorphDemo> with SingleTickerProviderStateMixin {
late AnimationController _ctrl;
double _time = 0;
int _currentIndex = 0;
final List<SuperShape> _shapes = [
SuperShape(m: 4, n1: 2, n2: 2, n3: 2),
SuperShape(m: 5, n1: 0.5, n2: 1, n3: 1),
SuperShape(m: 6, n1: 0.3, n2: 1, n3: 1),
SuperShape(m: 8, n1: 0.4, n2: 1, n3: 1),
SuperShape(m: 4, n1: 4, n2: 4, n3: 4),
];
void initState() {
super.initState();
_ctrl = AnimationController(vsync: this, duration: const Duration(milliseconds: 16))..repeat();
_ctrl.addListener(_update);
}
void _update() {
_time += 0.016;
if (_time > 3) { _time = 0; _currentIndex = (_currentIndex + 1) % _shapes.length; }
setState(() {});
}
void dispose() { _ctrl.removeListener(_update); _ctrl.dispose(); super.dispose(); }
Widget build(BuildContext context) {
final current = _shapes[_currentIndex];
final next = _shapes[(_currentIndex + 1) % _shapes.length];
final t = _time / 3;
final morphed = SuperShape(
m: _lerp(current.m, next.m, t),
n1: _lerp(current.n1, next.n1, t),
n2: _lerp(current.n2, next.n2, t),
n3: _lerp(current.n3, next.n3, t),
);
return Scaffold(
appBar: AppBar(title: const Text('形态变换')),
body: Column(children: [
Expanded(child: CustomPaint(painter: MorphPainter(morphed, t), size: Size.infinite)),
Padding(
padding: const EdgeInsets.all(16),
child: Text('变换进度: ${(t * 100).toStringAsFixed(0)}%', style: const TextStyle(color: Colors.white70)),
),
]),
);
}
double _lerp(double a, double b, double t) => a + (b - a) * t;
}
class MorphPainter extends CustomPainter {
final SuperShape shape;
final double progress;
MorphPainter(this.shape, this.progress);
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final scale = min(size.width, size.height) * 0.4;
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
final points = shape.points(360, scale, center);
final path = Path();
for (int i = 0; i < points.length; i++) {
if (i == 0) path.moveTo(points[i].dx, points[i].dy);
else path.lineTo(points[i].dx, points[i].dy);
}
path.close();
final hue = progress * 360;
canvas.drawPath(path, Paint()..shader = RadialGradient(colors: [
HSVColor.fromAHSV(1, hue, 0.8, 1).toColor(),
HSVColor.fromAHSV(1, (hue + 60) % 360, 0.8, 1).toColor(),
]).createShader(Rect.fromCircle(center: center, radius: scale)));
canvas.drawPath(path, Paint()..color = Colors.white..style = PaintingStyle.stroke..strokeWidth = 2);
}
bool shouldRepaint(covariant MorphPainter old) => true;
}
/// 自然形态演示
class NaturalShapesDemo extends StatelessWidget {
const NaturalShapesDemo({super.key});
Widget build(BuildContext context) {
final natural = [
('玫瑰', SuperShape(m: 5, n1: 0.2, n2: 1, n3: 1), Colors.red),
('向日葵', SuperShape(m: 12, n1: 0.3, n2: 1, n3: 1), Colors.yellow),
('海星', SuperShape(m: 5, n1: 0.4, n2: 1, n3: 1), Colors.orange),
('雪花', SuperShape(m: 6, n1: 0.5, n2: 1, n3: 1), Colors.lightBlue),
('三叶草', SuperShape(m: 3, n1: 0.3, n2: 1, n3: 1), Colors.green),
('蝴蝶', SuperShape(m: 2, n1: 0.2, n2: 1.5, n3: 1), Colors.purple),
];
return Scaffold(
appBar: AppBar(title: const Text('自然形态')),
body: ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: natural.length,
itemBuilder: (ctx, i) {
final (name, shape, color) = natural[i];
return Card(
margin: const EdgeInsets.only(bottom: 8),
child: ListTile(
leading: SizedBox(width: 50, height: 50, child: CustomPaint(painter: NaturalPainter(shape, color), size: Size.infinite)),
title: Text(name),
trailing: const Icon(Icons.chevron_right),
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => NaturalDetailPage(name: name, shape: shape, color: color))),
),
);
},
),
);
}
}
class NaturalPainter extends CustomPainter {
final SuperShape shape;
final Color color;
NaturalPainter(this.shape, this.color);
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final scale = min(size.width, size.height) * 0.4;
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
final points = shape.points(360, scale, center);
final path = Path();
for (int i = 0; i < points.length; i++) {
if (i == 0) path.moveTo(points[i].dx, points[i].dy);
else path.lineTo(points[i].dx, points[i].dy);
}
path.close();
canvas.drawPath(path, Paint()..color = color.withOpacity(0.5)..style = PaintingStyle.fill);
canvas.drawPath(path, Paint()..color = color..style = PaintingStyle.stroke..strokeWidth = 2);
}
bool shouldRepaint(covariant NaturalPainter old) => false;
}
class NaturalDetailPage extends StatefulWidget {
final String name;
final SuperShape shape;
final Color color;
const NaturalDetailPage({required this.name, required this.shape, required this.color, super.key});
State<NaturalDetailPage> createState() => _NaturalDetailPageState();
}
class _NaturalDetailPageState extends State<NaturalDetailPage> with SingleTickerProviderStateMixin {
late AnimationController _ctrl;
double _time = 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: Text(widget.name)),
body: CustomPaint(painter: NaturalDetailPainter(widget.shape, widget.color, _time), size: Size.infinite),
);
}
}
class NaturalDetailPainter extends CustomPainter {
final SuperShape shape;
final Color color;
final double time;
NaturalDetailPainter(this.shape, this.color, this.time);
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final baseScale = min(size.width, size.height) * 0.35;
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = const Color(0xFF0a0a15));
for (int layer = 4; layer >= 0; layer--) {
final scale = baseScale * (1 + layer * 0.15);
final rotation = time * 0.5 + layer * 0.2;
final opacity = 0.2 + (4 - layer) * 0.15;
final points = shape.points(360, scale, center);
final path = Path();
for (int i = 0; i < points.length; i++) {
final rotatedX = center.dx + (points[i].dx - center.dx) * cos(rotation) - (points[i].dy - center.dy) * sin(rotation);
final rotatedY = center.dy + (points[i].dx - center.dx) * sin(rotation) + (points[i].dy - center.dy) * cos(rotation);
if (i == 0) path.moveTo(rotatedX, rotatedY);
else path.lineTo(rotatedX, rotatedY);
}
path.close();
canvas.drawPath(path, Paint()..color = color.withOpacity(opacity)..style = PaintingStyle.fill);
canvas.drawPath(path, Paint()..color = color..style = PaintingStyle.stroke..strokeWidth = 1);
}
}
bool shouldRepaint(covariant NaturalDetailPainter old) => true;
}
📝 四、数学原理深入解析
📐 4.1 极坐标变换
笛卡尔坐标与极坐标:
极坐标 (r, θ) 到笛卡尔坐标 (x, y):
x = r·cos(θ)
y = r·sin(θ)
笛卡尔坐标到极坐标:
r = √(x² + y²)
θ = arctan(y/x)
超形状在极坐标中定义:
r(θ) = f(θ)
转换为笛卡尔:
x(θ) = f(θ)·cos(θ)
y(θ) = f(θ)·sin(θ)
切线与法线:
切向量:
dx/dθ = dr/dθ·cos(θ) - r·sin(θ)
dy/dθ = dr/dθ·sin(θ) + r·cos(θ)
切线斜率:
dy/dx = (dy/dθ) / (dx/dθ)
法向量:
nx = -dy/dθ
ny = dx/dθ
单位法向量:
n̂ = (nx, ny) / √(nx² + ny²)
🔄 4.2 曲率分析
曲率公式:
对于参数曲线 (x(θ), y(θ)):
κ = |x'y'' - y'x''| / (x'² + y'²)^(3/2)
对于超形状:
x(θ) = r(θ)·cos(θ)
y(θ) = r(θ)·sin(θ)
x' = r'·cos(θ) - r·sin(θ)
y' = r'·sin(θ) + r·cos(θ)
x'' = r''·cos(θ) - 2r'·sin(θ) - r·cos(θ)
y'' = r''·sin(θ) + 2r'·cos(θ) - r·sin(θ)
曲率半径:R = 1/|κ|
尖点分析:
当 n₁ < 1 时,超形状出现尖点
在尖点处:
- 曲率趋于无穷
- 切向量方向突变
- 曲线不可微
尖点位置:
当 cos(mθ/4) = 0 或 sin(mθ/4) = 0 时
r(θ) 可能趋于无穷或零
🌸 4.3 面积与周长
面积积分:
极坐标下的面积公式:
A = ½∫₀^(2π) r²(θ)dθ
对于超形状:
A = ½∫₀^(2π) [|cos(mθ/4)/a|^(n₂) + |sin(mθ/4)/b|^(n₃)]^(-2/n₁)dθ
特殊情况(n₁ = n₂ = n₃ = n):
A = 4ab·Γ(1 + 1/n)² / Γ(1 + 2/n)
验证:
- n = 2(椭圆):A = πab ✓
- n → ∞(矩形):A = 4ab ✓
周长积分:
弧长公式:
L = ∫₀^(2π) √(r² + (dr/dθ)²)dθ
对于超形状,需要数值积分
近似公式(n₁ = n₂ = n₃ = n):
L ≈ 4(a + b)·Γ(1 + 1/n)² / Γ(1 + 2/n)·E(e)
其中 E(e) 是第二类完全椭圆积分
🎯 4.4 形态分类
按参数分类:
根据 n₁ 分类:
n₁ > 1:凸形状
- 曲线向外凸
- 无凹陷
- 例:椭圆、超椭圆
n₁ = 1:临界形状
- 直边
- 例:菱形
n₁ < 1:凹形状
- 有凹陷或尖角
- 例:星形、花瓣
n₁ → 0:极端形状
- 深度凹陷
- 十字形或更复杂
按对称性分类:
根据 m 分类:
m = 1:无对称轴
m = 2:二重对称(蝴蝶形)
m = 3:三重对称(三叶草)
m = 4:四重对称(菱形、方形)
m = 5:五重对称(五角星)
m = 6:六重对称(六角星、雪花)
对称性与自然形态:
- 花朵:m = 花瓣数
- 海星:m = 触手数
- 雪花:m = 6
🔬 五、高级应用场景
🎨 5.1 建筑设计
超椭圆在建筑中的应用:
class ArchitecturalShape {
static SuperShape sergiCalderonPillar() {
return SuperShape(m: 4, n1: 2.5, n2: 2.5, n3: 2.5);
}
static SuperShape tableTop(double aspectRatio) {
return SuperShape(m: 4, n1: 3, n2: 3, n3: 3, a: aspectRatio, b: 1);
}
static List<Offset> generateFloorPlan(SuperShape shape, double scale) {
return shape.points(360, scale, Offset.zero);
}
}
🌐 5.2 UI 设计
按钮和卡片形状:
class SuperShapeBorder extends ShapeBorder {
final SuperShape shape;
final double scale;
SuperShapeBorder({required this.shape, required this.scale});
Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
final center = Offset(rect.left + rect.width / 2, rect.top + rect.height / 2);
return shape.toPath(360, scale, center);
}
EdgeInsetsGeometry get dimensions => EdgeInsets.zero;
Path getInnerPath(Rect rect, {TextDirection? textDirection}) => getOuterPath(rect);
void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) {}
ShapeBorder scale(double t) => SuperShapeBorder(shape: shape, scale: scale * t);
}
📱 5.3 鸿蒙多端适配
响应式形状生成:
class AdaptiveSuperShape {
static SuperShape forDevice(BuildContext context) {
final size = MediaQuery.of(context).size;
final aspectRatio = size.width / size.height;
return SuperShape(
m: 4,
n1: 2.5,
n2: 2.5,
n3: 2.5,
a: aspectRatio > 1 ? aspectRatio : 1,
b: aspectRatio < 1 ? 1 / aspectRatio : 1,
);
}
static double getOptimalScale(BuildContext context) {
final size = MediaQuery.of(context).size;
return min(size.width, size.height) * 0.4;
}
}
📊 六、性能优化策略
⚡ 6.1 点缓存
预计算优化:
class SuperShapeCache {
static final Map<String, List<Offset>> _cache = {};
static List<Offset> getPoints(SuperShape shape, int count, double scale, Offset center) {
final key = '${shape.m}_${shape.n1}_${shape.n2}_${shape.n3}_${shape.a}_${shape.b}_$count';
return _cache.putIfAbsent(key, () {
final basePoints = shape.points(count, 1, Offset.zero);
return basePoints.map((p) => Offset(p.dx * scale + center.dx, p.dy * scale + center.dy)).toList();
});
}
static void clearCache() => _cache.clear();
}
💾 6.2 GPU 加速
Shader 优化:
class SuperShapeShader {
static String fragmentShader() => '''
uniform float u_m;
uniform float u_n1;
uniform float u_n2;
uniform float u_n3;
uniform vec2 u_resolution;
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution;
vec2 center = vec2(0.5, 0.5);
vec2 p = uv - center;
float theta = atan(p.y, p.x);
float r = length(p);
float term1 = pow(abs(cos(u_m * theta / 4.0)), u_n2);
float term2 = pow(abs(sin(u_m * theta / 4.0)), u_n3);
float shapeR = pow(term1 + term2, -1.0 / u_n1);
float inside = step(r, shapeR * 0.4);
gl_FragColor = vec4(vec3(inside), 1.0);
}
''';
}
🎓 七、学习资源与拓展
📚 推荐阅读
| 主题 | 资源 | 难度 |
|---|---|---|
| 超椭圆 | Piet Hein 的设计哲学 | ⭐ |
| Gielis 公式 | Johan Gielis 的论文 | ⭐⭐ |
| 极坐标几何 | 《极坐标与曲线》 | ⭐⭐ |
| 自然形态 | 《On Growth and Form》 | ⭐⭐⭐ |
🔗 相关项目
- Superformula Explorer:在线超形状探索工具
- Processing:创意编程平台
- Generative Design:生成设计资源
📝 八、总结
本篇文章深入探讨了超形状与超椭圆的数学原理及其在 Flutter 中的可视化实现。
✅ 核心知识点回顾
| 知识点 | 说明 |
|---|---|
| 📐 超椭圆方程 | 介于椭圆和矩形之间 |
| 🌸 Gielis 公式 | 通用超形状描述 |
| 🔄 参数影响 | m, n₁, n₂, n₃ 的作用 |
| 🎯 自然形态 | 植物、动物的形状模拟 |
| ⚡ 形态变换 | 平滑过渡算法 |
⭐ 最佳实践要点
- ✅ 使用缓存优化重复计算
- ✅ 合理设置点数平衡精度与性能
- ✅ 注意 n < 1 时的尖点处理
- ✅ 利用对称性减少计算量
🚀 进阶方向
- 🔮 三维超曲面
- ✨ 动态形态动画
- 📊 形状识别与分类
- 🎨 生成艺术设计
💡 提示:本文代码基于 Flutter for Harmony 开发,可在鸿蒙设备上流畅运行。
更多推荐

所有评论(0)