import 'dart:async'; import 'dart:math'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; // 注意:运行前需要在 pubspec.yaml 添加 image_gallery_saver 和 permission_handler import 'package:image_gallery_saver/image_gallery_saver.dart'; import 'package:permission_handler/permission_handler.dart'; import '../../core/app_ads_tools.dart'; /// 粒子效果类型枚举 enum EffectType { fireflies, // 萤火虫/漂浮粒子 geometric, // 几何连线 snow, // 飘雪 galaxy, // 旋转星系 } class ParticleHomePage extends StatefulWidget { const ParticleHomePage({super.key}); @override State createState() => _ParticleHomePageState(); } class _ParticleHomePageState extends State with SingleTickerProviderStateMixin { // 用于动画循环的控制器 late AnimationController _controller; // 粒子列表 final List _particles = []; // 随机数生成器 final Random _random = Random(); // 当前选择的效果 EffectType _currentEffect = EffectType.fireflies; // 用于截屏的 Key final GlobalKey _repaintKey = GlobalKey(); // 屏幕尺寸缓存 Size _screenSize = Size.zero; @override void initState() { super.initState(); // 初始化动画控制器,无限循环 _controller = AnimationController( vsync: this, duration: const Duration(seconds: 1), )..repeat(); // 驱动界面刷新 // 监听动画帧,更新粒子状态 _controller.addListener(_updateParticles); } @override void dispose() { _controller.dispose(); super.dispose(); } /// 初始化或重置粒子系统 void _initParticles(Size size) { _particles.clear(); int count = 0; // 根据不同效果设置粒子数量 switch (_currentEffect) { case EffectType.fireflies: count = 100; break; case EffectType.geometric: count = 60; break; case EffectType.snow: count = 150; break; case EffectType.galaxy: count = 200; break; } for (int i = 0; i < count; i++) { _particles.add(Particle.random(size, _random, _currentEffect)); } } /// 每一帧更新粒子位置 void _updateParticles() { if (_screenSize == Size.zero) return; for (var particle in _particles) { particle.update(_screenSize, _currentEffect); } // 触发重绘 setState(() {}); } /// 切换效果 void _switchEffect() { setState(() { int nextIndex = (_currentEffect.index + 1) % EffectType.values.length; _currentEffect = EffectType.values[nextIndex]; _initParticles(_screenSize); }); } /// 保存截图到相册 Future _saveToGallery() async { try { // 1. 请求存储权限 var status = await Permission.storage.request(); // Android 13+ 可能需要 photos 权限,这里做简单处理,实际需更严谨判断 if (status.isDenied) { status = await Permission.photos.request(); } if (status.isGranted || await Permission.storage.isGranted || await Permission.photos.isGranted) { // 2. 获取 RenderRepaintBoundary RenderRepaintBoundary? boundary = _repaintKey.currentContext?.findRenderObject() as RenderRepaintBoundary?; if (boundary == null) return; // 3. 转换为图片数据 (高像素密度,保证壁纸清晰度) ui.Image image = await boundary.toImage(pixelRatio: 3.0); ByteData? byteData = await image.toByteData( format: ui.ImageByteFormat.png, ); if (byteData != null) { final result = await ImageGallerySaver.saveImage( byteData.buffer.asUint8List(), quality: 100, name: "particle_wallpaper_${DateTime.now().millisecondsSinceEpoch}", ); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( result['isSuccess'] ? 'Saved to Gallery!' : 'Failed to save.', ), backgroundColor: result['isSuccess'] ? Colors.green : Colors.red, ), ); } } } else { if (mounted) { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('Permission denied.'))); } } } catch (e) { debugPrint(e.toString()); if (mounted) { ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text('Error: $e'))); } } } @override Widget build(BuildContext context) { // 获取屏幕尺寸并在第一次时初始化粒子 final size = MediaQuery.of(context).size; if (_screenSize != size) { _screenSize = size; _initParticles(size); } return Scaffold( body: Stack( children: [ // 1. 绘图层 (被 RepaintBoundary 包裹以用于截图) RepaintBoundary( key: _repaintKey, child: Container( color: Colors.black, // 背景色 width: double.infinity, height: double.infinity, child: CustomPaint( painter: ParticlePainter( particles: _particles, effect: _currentEffect, ), ), ), ), // 2. 左上角返回按钮 Positioned( top: MediaQuery.of(context).padding.top + 8, left: 8, child: Container( decoration: BoxDecoration( gradient: LinearGradient( colors: [ Colors.purple.withValues(alpha: 0.3), Colors.blue.withValues(alpha: 0.3), ], ), shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Colors.purple.withValues(alpha: 0.3), blurRadius: 8, spreadRadius: 1, ), ], ), child: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () => Navigator.pop(context), tooltip: 'Back', ), ), ), // 顶部标题卡片 Positioned( top: MediaQuery.of(context).padding.top + 16, left: 0, right: 0, child: Center( child: Container( padding: const EdgeInsets.symmetric( horizontal: 24, vertical: 12, ), decoration: BoxDecoration( gradient: LinearGradient( colors: [ Colors.purple.withValues(alpha: 0.3), Colors.blue.withValues(alpha: 0.3), ], ), borderRadius: BorderRadius.circular(30), border: Border.all( color: Colors.white.withValues(alpha: 0.2), width: 1, ), boxShadow: [ BoxShadow( color: Colors.purple.withValues(alpha: 0.2), blurRadius: 20, spreadRadius: 2, ), ], ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.2), shape: BoxShape.circle, ), child: const Icon( Icons.auto_awesome, color: Colors.white, size: 20, ), ), const SizedBox(width: 12), const Text( 'AI Generator', style: TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold, letterSpacing: 0.5, ), ), ], ), ), ), ), // 3. UI 控制层 (半透明,不影响视觉) Positioned( bottom: 40, left: 16, right: 16, child: Column( mainAxisSize: MainAxisSize.min, children: [ // 效果选择器 Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ Colors.purple.withValues(alpha: 0.3), Colors.blue.withValues(alpha: 0.3), ], ), borderRadius: BorderRadius.circular(20), border: Border.all( color: Colors.white.withValues(alpha: 0.2), width: 1, ), boxShadow: [ BoxShadow( color: Colors.purple.withValues(alpha: 0.3), blurRadius: 20, spreadRadius: 2, ), ], ), child: Column( children: [ Row( children: [ const Icon( Icons.palette, color: Colors.white, size: 20, ), const SizedBox(width: 8), Text( 'Effect: ${_currentEffect.name.toUpperCase()}', style: const TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600, ), ), ], ), const SizedBox(height: 12), Row( children: [ Expanded( child: _buildActionButton( icon: Icons.refresh, label: 'Switch Effect', onTap: _switchEffect, ), ), const SizedBox(width: 12), Expanded( child: _buildActionButton( icon: Icons.download, label: 'Save', onTap: ()async{ final bool adShown = await AppAdsTools.instance.showAd( AdPlacement.interstitial3, onAdClosed: () { _saveToGallery(); }, ); if (!adShown) { _saveToGallery(); } }, isPrimary: true, ), ), ], ), ], ), ), ], ), ), ], ), ); } Widget _buildActionButton({ required IconData icon, required String label, required VoidCallback onTap, bool isPrimary = false, }) { return GestureDetector( onTap: onTap, child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( gradient: isPrimary ? LinearGradient( colors: [Colors.purple.shade400, Colors.blue.shade400], ) : null, color: isPrimary ? null : Colors.white.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(16), border: Border.all( color: isPrimary ? Colors.white.withValues(alpha: 0.3) : Colors.white.withValues(alpha: 0.2), width: 1, ), boxShadow: isPrimary ? [ BoxShadow( color: Colors.purple.withValues(alpha: 0.4), blurRadius: 12, spreadRadius: 1, ), ] : null, ), child: Row( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ Icon(icon, color: Colors.white, size: 20), const SizedBox(width: 6), Flexible( child: Text( label, style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w600, fontSize: 13, ), overflow: TextOverflow.ellipsis, maxLines: 1, ), ), ], ), ), ); } } /// 粒子数据模型 class Particle { double x; double y; double vx; // X轴速度 double vy; // Y轴速度 double size; Color color; double life; // 生命周期 (0.0 - 1.0) double angle; // 用于旋转效果 Particle({ required this.x, required this.y, required this.vx, required this.vy, required this.size, required this.color, this.life = 1.0, this.angle = 0.0, }); /// 生成随机粒子 factory Particle.random(Size screenSize, Random random, EffectType type) { Color randomColor() { final colors = [ Colors.cyanAccent, Colors.purpleAccent, Colors.blueAccent, Colors.pinkAccent, Colors.tealAccent, ]; return colors[random.nextInt(colors.length)]; } double x = random.nextDouble() * screenSize.width; double y = random.nextDouble() * screenSize.height; double vx = (random.nextDouble() - 0.5) * 2; double vy = (random.nextDouble() - 0.5) * 2; double size = random.nextDouble() * 3 + 1; Color color = randomColor().withValues( alpha: random.nextDouble() * 0.5 + 0.3, ); if (type == EffectType.snow) { vy = random.nextDouble() * 2 + 1; // 向下落 vx = (random.nextDouble() - 0.5) * 0.5; // 轻微左右飘 color = Colors.white.withValues(alpha: random.nextDouble() * 0.8 + 0.2); } else if (type == EffectType.galaxy) { // 这里的 x, y 会在 update 中根据中心点重新计算,只需初始化角度 x = screenSize.width / 2; y = screenSize.height / 2; } return Particle( x: x, y: y, vx: vx, vy: vy, size: size, color: color, angle: random.nextDouble() * pi * 2, life: random.nextDouble(), ); } /// 更新粒子位置和状态 void update(Size size, EffectType type) { if (type == EffectType.galaxy) { // 星系模式:围绕中心旋转 double centerX = size.width / 2; double centerY = size.height / 2; angle += 0.01 * (vx.sign == 0 ? 1 : vx.sign); // 旋转速度 double radius = this.size * 30 + (life * 150); // 半径 x = centerX + cos(angle) * radius; y = centerY + sin(angle) * radius; return; } x += vx; y += vy; // 边界检测:超出屏幕则反弹或重置 if (type == EffectType.snow) { if (y > size.height) { y = -10; x = Random().nextDouble() * size.width; } } else { if (x < 0 || x > size.width) vx = -vx; if (y < 0 || y > size.height) vy = -vy; } } } /// 核心绘制逻辑 class ParticlePainter extends CustomPainter { final List particles; final EffectType effect; ParticlePainter({required this.particles, required this.effect}); @override void paint(Canvas canvas, Size size) { final paint = Paint()..strokeCap = StrokeCap.round; for (var i = 0; i < particles.length; i++) { var p = particles[i]; // 1. 绘制粒子本体 paint.color = p.color; paint.strokeWidth = p.size; // 不同的绘制形状 if (effect == EffectType.snow) { // 雪花带一点模糊 paint.maskFilter = const MaskFilter.blur(BlurStyle.normal, 2); canvas.drawCircle(Offset(p.x, p.y), p.size, paint); } else { canvas.drawCircle(Offset(p.x, p.y), p.size / 2, paint); } // 2. 几何模式下的连线效果 if (effect == EffectType.geometric) { _drawConnections(canvas, p, i, size); } } } /// 绘制连线:如果两个粒子距离够近,就画一条线 void _drawConnections( Canvas canvas, Particle p1, int currentIndex, Size size, ) { Paint linePaint = Paint()..strokeWidth = 1.0; double connectDistance = 100.0; // 连线阈值 for (var j = currentIndex + 1; j < particles.length; j++) { var p2 = particles[j]; double dx = p1.x - p2.x; double dy = p1.y - p2.y; double dist = sqrt(dx * dx + dy * dy); if (dist < connectDistance) { // 距离越近,线条越不透明 double opacity = 1.0 - (dist / connectDistance); linePaint.color = Colors.cyanAccent.withValues(alpha: opacity * 0.5); canvas.drawLine(Offset(p1.x, p1.y), Offset(p2.x, p2.y), linePaint); } } } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return true; // 总是重绘以实现动画 } }