import 'dart:math' as math; import 'package:flutter/material.dart'; /// 动态渐变背景 — 精确复刻 HTML styles.css 中的 Animated Gradient Background /// 5 层渐变 + 5 组不同节奏的有机流动动画 class AnimatedGradientBackground extends StatefulWidget { const AnimatedGradientBackground({super.key}); @override State createState() => _AnimatedGradientBackgroundState(); } class _AnimatedGradientBackgroundState extends State with TickerProviderStateMixin { // 5 个不同速度的动画控制器,对应 HTML 的 organicFlow1~5 late final List _controllers; @override void initState() { super.initState(); _controllers = [ AnimationController(vsync: this, duration: const Duration(seconds: 20)), AnimationController(vsync: this, duration: const Duration(seconds: 25)), AnimationController(vsync: this, duration: const Duration(seconds: 30)), AnimationController(vsync: this, duration: const Duration(seconds: 35)), AnimationController(vsync: this, duration: const Duration(seconds: 28)), ]; for (final c in _controllers) { c.repeat(); } } @override void dispose() { for (final c in _controllers) { c.dispose(); } super.dispose(); } @override Widget build(BuildContext context) { return Positioned.fill( child: Container( color: const Color(0xFFFEFEFE), child: Stack( children: [ // Layer 1: Soft pink from bottom-left (organicFlow1, 20s) _buildLayer( controller: _controllers[0], gradients: const [ _GradientDef( center: Alignment(-0.6, 0.6), radiusX: 1.2, radiusY: 0.8, color: Color(0x99FFC8DC), // rgba(255, 200, 220, 0.6) stops: [0.0, 0.5], ), _GradientDef( center: Alignment(-0.4, 0.2), radiusX: 0.8, radiusY: 0.6, color: Color(0x66FFB4C8), // rgba(255, 180, 200, 0.4) stops: [0.0, 0.4], ), ], keyframes: const [ _Keyframe(0.00, 0, 0, 0, 1.0), _Keyframe(0.25, 0.05, -0.08, 2, 1.02), _Keyframe(0.50, -0.03, 0.05, -1, 0.98), _Keyframe(0.75, 0.08, 0.03, 1, 1.01), _Keyframe(1.00, 0, 0, 0, 1.0), ], ), // Layer 2: Mint/Cyan from top-right (organicFlow2, 25s) _buildLayer( controller: _controllers[1], gradients: const [ _GradientDef( center: Alignment(0.6, -0.6), radiusX: 1.0, radiusY: 0.7, color: Color(0x80B4F0F0), // rgba(180, 240, 240, 0.5) stops: [0.0, 0.45], ), _GradientDef( center: Alignment(0.4, -0.2), radiusX: 0.7, radiusY: 0.9, color: Color(0x66C8F5F5), // rgba(200, 245, 245, 0.4) stops: [0.0, 0.4], ), ], keyframes: const [ _Keyframe(0.00, 0, 0, 0, 1.0), _Keyframe(0.33, -0.08, 0.06, -2, 1.03), _Keyframe(0.66, 0.06, -0.05, 2, 0.97), _Keyframe(1.00, 0, 0, 0, 1.0), ], ), // Layer 3: Lavender/Purple diagonal (organicFlow3, 30s) _buildLayer( controller: _controllers[2], gradients: const [ _GradientDef( center: Alignment(0.2, 0.0), radiusX: 0.9, radiusY: 0.8, color: Color(0x73E6D2FA), // rgba(230, 210, 250, 0.45) stops: [0.0, 0.45], ), _GradientDef( center: Alignment(-0.2, 0.4), radiusX: 0.6, radiusY: 0.5, color: Color(0x59F0DCFF), // rgba(240, 220, 255, 0.35) stops: [0.0, 0.35], ), ], keyframes: const [ _Keyframe(0.0, 0, 0, 0, 1.0), _Keyframe(0.2, 0.04, 0.07, 1, 1.02), _Keyframe(0.4, -0.06, -0.04, -2, 0.98), _Keyframe(0.6, 0.07, -0.06, 2, 1.01), _Keyframe(0.8, -0.04, 0.05, -1, 0.99), _Keyframe(1.0, 0, 0, 0, 1.0), ], ), // Layer 4 (::before): Warm pink top-left + cool cyan bottom-right (organicFlow4, 35s) _buildLayer( controller: _controllers[3], sizeMultiplier: 1.5, gradients: const [ _GradientDef( center: Alignment(-0.7, -0.4), radiusX: 0.6, radiusY: 0.4, color: Color(0x80FFE6F0), // rgba(255, 230, 240, 0.5) stops: [0.0, 0.4], ), _GradientDef( center: Alignment(0.7, 0.2), radiusX: 0.5, radiusY: 0.7, color: Color(0x66DCFAFA), // rgba(220, 250, 250, 0.4) stops: [0.0, 0.35], ), ], keyframes: const [ _Keyframe(0.0, 0, 0, 0, 1.0), _Keyframe(0.25, -0.05, 0.07, -1, 1.01), _Keyframe(0.50, 0.06, -0.04, 2, 0.99), _Keyframe(0.75, -0.03, 0.05, -1, 1.02), _Keyframe(1.0, 0, 0, 0, 1.0), ], ), // Layer 5 (::after): Pink bottom + Cyan top-right (organicFlow5, 28s) _buildLayer( controller: _controllers[4], sizeMultiplier: 1.2, gradients: const [ _GradientDef( center: Alignment(0.0, 0.8), radiusX: 0.7, radiusY: 0.5, color: Color(0x59FFD2E6), // rgba(255, 210, 230, 0.35) stops: [0.0, 0.4], ), _GradientDef( center: Alignment(0.8, -0.8), radiusX: 0.4, radiusY: 0.6, color: Color(0x4DC8F0FF), // rgba(200, 240, 255, 0.3) stops: [0.0, 0.35], ), ], keyframes: const [ _Keyframe(0.0, 0, 0, 0, 1.0), _Keyframe(0.33, 0.04, -0.06, 1, 1.02), _Keyframe(0.66, -0.06, 0.04, -2, 0.98), _Keyframe(1.0, 0, 0, 0, 1.0), ], ), ], ), ), ); } Widget _buildLayer({ required AnimationController controller, required List<_GradientDef> gradients, required List<_Keyframe> keyframes, double sizeMultiplier = 2.0, }) { return AnimatedBuilder( animation: controller, builder: (context, child) { final t = controller.value; final frame = _interpolateKeyframes(keyframes, t); return Transform( alignment: Alignment.center, transform: Matrix4.identity() ..translate( frame.tx * MediaQuery.of(context).size.width, frame.ty * MediaQuery.of(context).size.height, ) ..rotateZ(frame.rotate * math.pi / 180) ..scale(frame.scale), child: child, ); }, child: SizedBox.expand( child: CustomPaint( painter: _GradientLayerPainter(gradients, sizeMultiplier), ), ), ); } _Keyframe _interpolateKeyframes(List<_Keyframe> keyframes, double t) { // 找到 t 所在的两个 keyframe 之间 int i = 0; for (i = 0; i < keyframes.length - 1; i++) { if (t <= keyframes[i + 1].time) break; } if (i >= keyframes.length - 1) i = keyframes.length - 2; final a = keyframes[i]; final b = keyframes[i + 1]; final segT = (b.time - a.time) == 0 ? 0.0 : (t - a.time) / (b.time - a.time); // ease-in-out 缓动 final eased = _easeInOut(segT); return _Keyframe( t, a.tx + (b.tx - a.tx) * eased, a.ty + (b.ty - a.ty) * eased, a.rotate + (b.rotate - a.rotate) * eased, a.scale + (b.scale - a.scale) * eased, ); } double _easeInOut(double t) { return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t; } } class _Keyframe { final double time; // 0~1 final double tx; // translateX(比例) final double ty; // translateY(比例) final double rotate; // degrees final double scale; const _Keyframe(this.time, this.tx, this.ty, this.rotate, this.scale); } class _GradientDef { final Alignment center; final double radiusX; final double radiusY; final Color color; final List stops; const _GradientDef({ required this.center, required this.radiusX, required this.radiusY, required this.color, required this.stops, }); } class _GradientLayerPainter extends CustomPainter { final List<_GradientDef> gradients; final double sizeMultiplier; _GradientLayerPainter(this.gradients, this.sizeMultiplier); @override void paint(Canvas canvas, Size size) { final w = size.width * sizeMultiplier; final h = size.height * sizeMultiplier; final offsetX = (size.width - w) / 2; final offsetY = (size.height - h) / 2; for (final g in gradients) { final cx = offsetX + w * (g.center.x + 1) / 2; final cy = offsetY + h * (g.center.y + 1) / 2; final rx = w * g.radiusX / 2; final ry = h * g.radiusY / 2; final r = math.max(rx, ry); final gradient = RadialGradient( center: Alignment.center, radius: 1.0, colors: [g.color, g.color.withOpacity(0)], stops: g.stops, ); final rect = Rect.fromCenter( center: Offset(cx, cy), width: rx * 2, height: ry * 2, ); final paint = Paint() ..shader = gradient.createShader(rect) ..blendMode = BlendMode.srcOver; canvas.drawOval(rect, paint); } } @override bool shouldRepaint(covariant _GradientLayerPainter oldDelegate) => false; }