import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; class SphereCloud extends StatefulWidget { SphereCloud({ super.key, required this.items, this.radius = 140, required this.itemSize, this.cameraZ = 360, // 相机离球心距离,越大透视越弱 this.focal = 320, // 焦距,影响投影与缩放 this.minScale = 0.45, this.maxScale = 1.25, }); final List items; final double radius; final double itemSize; final double cameraZ; final double focal; final double minScale; final double maxScale; @override State createState() => _SphereCloudState(); } class _SphereCloudState extends State { late final List<_Point3> _points; // 当前旋转角(弧度) double _rotX = 0.0; double _rotY = 0.0; @override void initState() { super.initState(); _points = _genFibonacciSphere(widget.items.length, widget.radius); } @override void didUpdateWidget(covariant SphereCloud oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.items.length != widget.items.length || oldWidget.radius != widget.radius) { _points ..clear() ..addAll(_genFibonacciSphere(widget.items.length, widget.radius)); } } @override Widget build(BuildContext context) { return LayoutBuilder(builder: (_, c) { final cx = c.maxWidth / 2; final cy = c.maxHeight / 2; // 计算每个点投影后的 2D 信息 final projected = <_ProjectedItem>[]; for (int i = 0; i < _points.length; i++) { final p = _rotate(_points[i], _rotX, _rotY); // 透视:避免 cameraZ - z 过小导致爆炸 final denom = (widget.cameraZ - p.z).clamp(40.0, 1e9); final perspective = widget.focal / denom; final x2 = cx + p.x * perspective; final y2 = cy + p.y * perspective; final scale = perspective.clamp(widget.minScale, widget.maxScale); projected.add(_ProjectedItem( index: i, x: x2, y: y2, z: p.z, scale: scale, )); } // 按 z 排序:远->近 projected.sort((a, b) => a.z.compareTo(b.z)); return GestureDetector( onPanUpdate: (d) { // 你可以按手感调整系数 setState(() { _rotY += d.delta.dx * 0.01; _rotX -= d.delta.dy * 0.01; }); }, child: Stack( clipBehavior: Clip.none, children: [ for (final it in projected) Positioned( left: it.x - widget.itemSize / 2, top: it.y - widget.itemSize / 2, child: Transform.scale( scale: it.scale, child: SizedBox( width: widget.itemSize, height: widget.itemSize, child: widget.items[it.index], ), ), ), ], ), ); }); } // Fibonacci sphere:简单、分布均匀(比随机更像“均匀铺满球面”) List<_Point3> _genFibonacciSphere(int n, double r) { if (n <= 0) return []; final pts = <_Point3>[]; final offset = 2.0 / n; final increment = math.pi * (3.0 - math.sqrt(5.0)); // golden angle for (int i = 0; i < n; i++) { final y = ((i * offset) - 1) + (offset / 2); final rr = math.sqrt(1 - y * y); final phi = i * increment; final x = math.cos(phi) * rr; final z = math.sin(phi) * rr; pts.add(_Point3(x * r, y * r, z * r)); } return pts; } // 绕 X/Y 旋转(右手系) _Point3 _rotate(_Point3 p, double rx, double ry) { // rotate around X final cosX = math.cos(rx), sinX = math.sin(rx); var y1 = p.y * cosX - p.z * sinX; var z1 = p.y * sinX + p.z * cosX; // rotate around Y final cosY = math.cos(ry), sinY = math.sin(ry); var x2 = p.x * cosY + z1 * sinY; var z2 = -p.x * sinY + z1 * cosY; return _Point3(x2, y1, z2); } } class _Point3 { _Point3(this.x, this.y, this.z); final double x, y, z; } class _ProjectedItem { _ProjectedItem({ required this.index, required this.x, required this.y, required this.z, required this.scale, }); final int index; final double x, y, z; final double scale; }