chatapp3-flutter/lib/ui_kit/widgets/room/anim/room_entrance_screen.dart
2026-04-09 21:32:23 +08:00

272 lines
7.5 KiB
Dart

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<RoomAnimationQueueScreen> createState() =>
_RoomAnimationQueueScreenState();
}
class _RoomAnimationQueueScreenState extends State<RoomAnimationQueueScreen> {
final List<AnimationTask> _animationQueue = [];
bool _isQueueProcessing = false;
final Map<int, GlobalKey<_RoomEntranceAnimationState>> _animationKeys = {};
@override
void initState() {
super.initState();
Provider.of<RtmProvider>(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<RoomEntranceAnimation> createState() => _RoomEntranceAnimationState();
}
class _RoomEntranceAnimationState extends State<RoomEntranceAnimation>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<Offset> _offsetAnimation;
bool _isAnimating = false;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 4),
vsync: this,
);
_offsetAnimation = TweenSequence<Offset>([
TweenSequenceItem(
tween: Tween<Offset>(
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<Offset>(const Offset(0.0, 0.0)),
weight: 20.0,
),
TweenSequenceItem(
tween: Tween<Offset>(
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";
}
}