import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:yumi/app_localizations.dart'; import 'package:yumi/ui_kit/components/sc_compontent.dart'; import 'package:yumi/ui_kit/components/text/sc_text.dart'; import 'package:provider/provider.dart'; import 'package:yumi/services/audio/rtm_manager.dart'; import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart'; class RoomAnimationQueueScreen extends StatefulWidget { const RoomAnimationQueueScreen({super.key}); @override State createState() => _RoomAnimationQueueScreenState(); } class _RoomAnimationQueueScreenState extends State { final List _animationQueue = []; bool _isQueueProcessing = false; final Map> _animationKeys = {}; @override void initState() { super.initState(); Provider.of(context, listen: false).msgUserJoinListener = _msgUserJoinListener; } _msgUserJoinListener(Msg msg) { Future.delayed(Duration(milliseconds: 550), () { _addToQueue(msg); }); } void _addToQueue(Msg msg) { setState(() { final taskId = DateTime.now().millisecondsSinceEpoch; final animationKey = GlobalKey<_RoomEntranceAnimationState>(); _animationKeys[taskId] = animationKey; final task = AnimationTask( id: taskId, msg: msg, onComplete: () { if (_animationQueue.isNotEmpty && _animationQueue.first.id == taskId) { setState(() { _animationQueue.removeAt(0); _animationKeys.remove(taskId); }); if (_animationQueue.isNotEmpty) { _startNextAnimation(); } else { setState(() => _isQueueProcessing = false); } } }, ); _animationQueue.add(task); }); if (!_isQueueProcessing && _animationQueue.isNotEmpty) { setState(() => _isQueueProcessing = true); _startNextAnimation(); } } void _startNextAnimation({int retryCount = 0}) { if (_animationQueue.isEmpty) return; SchedulerBinding.instance.addPostFrameCallback((_) { if (_animationQueue.isNotEmpty) { final task = _animationQueue.first; final key = _animationKeys[task.id]; if (key?.currentState != null) { key!.currentState!._startAnimation(); } else if (retryCount < 3) { // 有限次重试,避免无限循环 Future.delayed(const Duration(milliseconds: 50), () { _startNextAnimation(retryCount: retryCount + 1); }); } else { // 重试多次失败后,跳过当前动画 print("动画启动失败,跳过当前任务"); task.onComplete(); } } }); } void _clearQueue() { setState(() { _animationQueue.clear(); _animationKeys.clear(); _isQueueProcessing = false; }); } @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(20.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Stack( children: [ if (_animationQueue.isNotEmpty) RoomEntranceAnimation( key: _animationKeys[_animationQueue.first.id], msg: _animationQueue.first.msg, onComplete: _animationQueue.first.onComplete, ), ], ), ], ), ); } } class AnimationTask { final int id; final Msg msg; final VoidCallback onComplete; AnimationTask({ required this.id, required this.msg, required this.onComplete, }); } class RoomEntranceAnimation extends StatefulWidget { final VoidCallback onComplete; final Msg msg; const RoomEntranceAnimation({ super.key, required this.onComplete, required this.msg, }); @override State createState() => _RoomEntranceAnimationState(); } class _RoomEntranceAnimationState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation _offsetAnimation; bool _isAnimating = false; @override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(seconds: 4), vsync: this, ); _offsetAnimation = TweenSequence([ TweenSequenceItem( tween: Tween( begin: const Offset(1.0, 0.0), end: const Offset(0.0, 0.0), ).chain(CurveTween(curve: Curves.easeOut)), weight: 40.0, ), TweenSequenceItem( tween: ConstantTween(const Offset(0.0, 0.0)), weight: 20.0, ), TweenSequenceItem( tween: Tween( begin: const Offset(0.0, 0.0), end: const Offset(-1.0, 0.0), ).chain(CurveTween(curve: Curves.easeIn)), weight: 40.0, ), ]).animate(_controller); _controller.addStatusListener((status) { if (status == AnimationStatus.completed) { setState(() => _isAnimating = false); widget.onComplete(); } }); } void _startAnimation() { if (_isAnimating) return; setState(() => _isAnimating = true); _controller.reset(); _controller.forward(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return SlideTransition( position: _offsetAnimation, child: Container( width: ScreenUtil().screenWidth * 0.8, height: 37.w, decoration: BoxDecoration( image: DecorationImage( image: AssetImage( getEntranceBg(""), ), fit: BoxFit.fill, ), ), child: Row( textDirection: TextDirection.ltr, children: [ Padding( padding: EdgeInsets.all(2.w), child: netImage( url: widget.msg.user?.userAvatar ?? "", width: 28.w, height: 28.w, shape: BoxShape.circle, ), ), SizedBox(width: 5.w), Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 2.w), socialchatNickNameText( maxWidth: 150.w, widget.msg.user?.userNickname ?? "", fontSize: 12.sp, type: "", needScroll: (widget.msg.user?.userNickname?.characters.length ?? 0) > 15, ), text( SCAppLocalizations.of(context)!.enterTheRoom, fontSize: 12.sp, lineHeight: 0.9, textColor: Colors.white, ), ], ), ], ), ), ); } String getEntranceBg(String type) { return "sc_images/room/entrance/sc_icon_room_entrance_no_vip_bg.png"; } }