import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'dart:math'; import 'dart:async'; import 'package:yumi/ui_kit/widgets/headdress/headdress_widget.dart'; import 'package:yumi/ui_kit/widgets/room/anim/room_entrance_widget.dart'; // 红包数据模型 class RedPacket { double x; // x坐标 double y; // y坐标 double speed; // 下落速度 double size; // 大小 double rotation; // 旋转角度 double rotationSpeed; // 旋转速度 Color color; // 红包颜色 bool isCollected; // 是否被收集 String id; // 唯一标识符 RedPacket({ required this.x, required this.y, required this.speed, required this.size, required this.rotation, required this.rotationSpeed, required this.color, this.isCollected = false, required this.id, }); } class RedPacketRainPage extends StatefulWidget { @override _RedPacketRainPageState createState() => _RedPacketRainPageState(); } class _RedPacketRainPageState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; final Random _random = Random(); final List _redPackets = []; final int _maxPackets = 50; DateTime? _startTime; int _durationSeconds = 30; int _collectedCount = 0; final List _redColors = [ Colors.red[400]!, Colors.red[500]!, Colors.red[600]!, Colors.red[700]!, Colors.red[800]!, Colors.red[900]!, ]; @override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: Duration(milliseconds: 350), )..addListener(_updateRain); _startRain(); Future.delayed(Duration(seconds: 3),(){ RoomEntranceQueueManager().addToQueue(AnimationQueueItem(resource: "sc_images/room/entrance/test3.svga",dynamicData: {"textReplacements":{"02":"3123123"}})); }); } void _startRain() { _startTime = DateTime.now(); _collectedCount = 0; _redPackets.clear(); _controller.repeat(); Timer(Duration(seconds: _durationSeconds), () { if (mounted) { _controller.stop(); } }); } void _updateRain() { if (!mounted) return; final screenWidth = MediaQuery.of(context).size.width; final screenHeight = MediaQuery.of(context).size.height; // 随机生成新红包 if (_redPackets.length < _maxPackets && _random.nextDouble() < 0.3) { _createNewPacket(screenWidth, screenHeight); } // 更新现有红包位置 for (int i = _redPackets.length - 1; i >= 0; i--) { final packet = _redPackets[i]; if (!packet.isCollected) { packet.y += packet.speed; packet.rotation += packet.rotationSpeed; if (packet.y > screenHeight + 100) { _redPackets.removeAt(i); continue; } } } setState(() {}); } void _createNewPacket(double screenWidth, double screenHeight) { _redPackets.add( RedPacket( x: _random.nextDouble() * screenWidth, y: -50, speed: 2 + _random.nextDouble() * 3, size: 30 + _random.nextDouble() * 20, rotation: _random.nextDouble() * 2 * pi, rotationSpeed: (_random.nextDouble() - 0.5) * 0.1, color: _redColors[_random.nextInt(_redColors.length)], id: '${DateTime.now().millisecondsSinceEpoch}_${_random.nextInt(1000)}', ), ); } void _collectPacket(String packetId) { final packetIndex = _redPackets.indexWhere( (packet) => packet.id == packetId, ); if (packetIndex != -1 && !_redPackets[packetIndex].isCollected) { setState(() { _redPackets[packetIndex].isCollected = true; _collectedCount++; }); // 收集动画效果 Future.delayed(Duration(milliseconds: 350), () { if (mounted) { setState(() { _redPackets.removeWhere((packet) => packet.id == packetId); }); } }); } } @override void dispose() { _controller.dispose(); _redPackets.clear(); super.dispose(); } @override Widget build(BuildContext context) { final remainingTime = _startTime != null ? max( 0, _durationSeconds - DateTime.now().difference(_startTime!).inSeconds, ) : _durationSeconds; return Scaffold( backgroundColor: Colors.black, body: RoomEntranceWidget( height: 90.w, ), ); } void _handleTap(Offset position) { // 从后往前遍历,这样点击检测会优先处理最上层的红包(最后绘制的) for (int i = _redPackets.length - 1; i >= 0; i--) { final packet = _redPackets[i]; if (!packet.isCollected && _isPointInRedPacket(position, packet)) { _collectPacket(packet.id); break; // 只处理最上层的一个红包 } } } bool _isPointInRedPacket(Offset point, RedPacket packet) { // 简化的点击区域检测 - 圆形区域 final distance = sqrt( pow(point.dx - packet.x, 2) + pow(point.dy - packet.y, 2), ); return distance < packet.size; } Widget _buildInfoCard(String title, String value) { return Container( padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: Colors.black54, borderRadius: BorderRadius.circular(16), border: Border.all(color: Colors.red), ), child: Column( children: [ Text(title, style: TextStyle(color: Colors.white, fontSize: 10)), Text( value, style: TextStyle( color: Colors.red, fontSize: 14, fontWeight: FontWeight.bold, ), ), ], ), ); } } class RedPacketPainter extends CustomPainter { final List redPackets; RedPacketPainter({required this.redPackets}); @override void paint(Canvas canvas, Size size) { final textPainter = TextPainter(textDirection: TextDirection.ltr); for (final packet in redPackets) { if (packet.isCollected) { _drawCollectedPacket(canvas, packet); } else { _drawRedPacket(canvas, packet, textPainter); } } } void _drawRedPacket( Canvas canvas, RedPacket packet, TextPainter textPainter, ) { canvas.save(); canvas.translate(packet.x, packet.y); canvas.rotate(packet.rotation); // 绘制红包主体 final packetPaint = Paint() ..color = packet.color ..style = PaintingStyle.fill; final rect = Rect.fromCenter( center: Offset.zero, width: packet.size, height: packet.size * 1.5, ); final rrect = RRect.fromRectAndRadius(rect, Radius.circular(8)); canvas.drawRRect(rrect, packetPaint); // 红包中间的黄色条纹 final stripePaint = Paint() ..color = Colors.yellow[700]! ..style = PaintingStyle.fill; final stripeRect = Rect.fromCenter( center: Offset.zero, width: packet.size * 0.8, height: packet.size * 0.2, ); canvas.drawRRect( RRect.fromRectAndRadius(stripeRect, Radius.circular(4)), stripePaint, ); // 红包文字 textPainter.text = TextSpan( text: '福', style: TextStyle( color: Colors.yellow[700], fontSize: packet.size * 0.4, fontWeight: FontWeight.bold, ), ); textPainter.layout(); textPainter.paint( canvas, Offset(-textPainter.width / 2, -textPainter.height / 2), ); canvas.restore(); } void _drawCollectedPacket(Canvas canvas, RedPacket packet) { final progress = (DateTime.now().millisecondsSinceEpoch % 300) / 300; final scale = 1.0 - progress; final opacity = 1.0 - progress; canvas.save(); canvas.translate(packet.x, packet.y); canvas.scale(scale); final packetPaint = Paint() ..color = packet.color.withOpacity(opacity) ..style = PaintingStyle.fill; final rect = Rect.fromCenter( center: Offset.zero, width: packet.size, height: packet.size * 1.5, ); final rrect = RRect.fromRectAndRadius(rect, Radius.circular(8)); canvas.drawRRect(rrect, packetPaint); canvas.restore(); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => true; }