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

735 lines
20 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import 'package:flutter_svga/flutter_svga.dart';
import 'dart:async';
import 'dart:typed_data';
import 'package:yumi/ui_kit/components/sc_tts.dart';
import 'package:yumi/shared/tools/sc_gift_vap_svga_manager.dart';
// 动画队列项
class AnimationQueueItem {
final String resource;
final Map<String, dynamic>? dynamicData; // 动态数据(文本、图片等)
final VoidCallback? onCompleted;
final Function(String)? onError;
Completer<void>? completer;
AnimationQueueItem({
required this.resource,
this.dynamicData,
this.onCompleted,
this.onError,
});
}
// 动态数据类型
class DynamicData {
final Map<String, String>? textReplacements; // 文本替换
final Map<String, Uint8List>? imageReplacements; // 图片替换(二进制数据)
final Map<String, String>? imageAssetPaths; // 图片资源路径
DynamicData({
this.textReplacements,
this.imageReplacements,
this.imageAssetPaths,
});
}
// 动画队列管理器
class RoomEntranceQueueManager {
static final RoomEntranceQueueManager _instance =
RoomEntranceQueueManager._internal();
factory RoomEntranceQueueManager() => _instance;
RoomEntranceQueueManager._internal();
// 队列列表
final List<AnimationQueueItem> _queue = [];
// 当前是否正在播放
bool _isPlaying = false;
// 当前播放器实例
_RoomEntranceWidgetState? _currentPlayer;
// 队列回调
void Function()? onQueueUpdated;
// 最大队列长度限制
int maxQueueLength = 20;
// 自动播放延迟时间(毫秒)
int autoPlayDelay = 500;
// 添加动画到队列
Future<void> addToQueue(AnimationQueueItem item) {
final completer = Completer<void>();
item.completer = completer;
// 检查队列长度限制
if (_queue.length >= maxQueueLength) {
// 移除最早的动画
final removedItem = _queue.removeAt(0);
removedItem.completer?.completeError('Queue overflow, item removed');
}
_queue.add(item);
// 通知队列更新
if (onQueueUpdated != null) {
onQueueUpdated!();
}
// 如果没有正在播放,则开始播放队列
if (!_isPlaying) {
_startNextAnimation();
}
return completer.future;
}
// 获取当前队列长度
int get queueLength => _queue.length;
// 获取等待中的队列项
List<AnimationQueueItem> get waitingItems =>
_queue.isNotEmpty && _isPlaying
? _queue.sublist(1)
: List<AnimationQueueItem>.from(_queue);
// 设置当前播放器
void setCurrentPlayer(_RoomEntranceWidgetState player) {
_currentPlayer = player;
}
// 清除当前播放器
void clearCurrentPlayer() {
_currentPlayer = null;
}
// 开始播放下一个动画
void _startNextAnimation() {
if (_queue.isEmpty) {
_isPlaying = false;
if (onQueueUpdated != null) {
onQueueUpdated!();
}
return;
}
_isPlaying = true;
final nextItem = _queue.first;
// 如果有延迟,则延迟播放
Future.delayed(Duration(milliseconds: autoPlayDelay), () {
if (_currentPlayer != null && _currentPlayer!.mounted) {
// 播放队列中的动画
_currentPlayer!.playFromQueue(
resource: nextItem.resource,
dynamicData:
nextItem.dynamicData != null
? DynamicData(
textReplacements: nextItem.dynamicData?['textReplacements'],
imageReplacements:
nextItem.dynamicData?['imageReplacements'],
imageAssetPaths: nextItem.dynamicData?['imageAssetPaths'],
)
: null,
onCompleted: () {
// 动画完成回调
if (nextItem.onCompleted != null) {
nextItem.onCompleted!();
}
// 完成Completer
nextItem.completer?.complete();
// 从队列中移除当前项
_queue.removeAt(0);
// 通知队列更新
if (onQueueUpdated != null) {
onQueueUpdated!();
}
// 播放下一个
_startNextAnimation();
},
onError: (error) {
// 错误处理
if (nextItem.onError != null) {
nextItem.onError!(error);
}
// 完成Completer带错误
nextItem.completer?.completeError(error);
// 从队列中移除当前项
_queue.removeAt(0);
// 通知队列更新
if (onQueueUpdated != null) {
onQueueUpdated!();
}
// 播放下一个
_startNextAnimation();
},
);
} else {
// 如果播放器不可用,跳过当前项
_queue.removeAt(0);
_startNextAnimation();
}
});
}
// 清空队列
void clearQueue() {
for (final item in _queue) {
item.completer?.completeError('Queue cleared');
}
_queue.clear();
_isPlaying = false;
if (onQueueUpdated != null) {
onQueueUpdated!();
}
}
// 移除指定位置的队列项
void removeAtIndex(int index) {
if (index >= 0 && index < _queue.length) {
final removedItem = _queue.removeAt(index);
removedItem.completer?.completeError('Item removed from queue');
if (onQueueUpdated != null) {
onQueueUpdated!();
}
}
}
// 获取队列状态
Map<String, dynamic> get queueStatus {
return {
'isPlaying': _isPlaying,
'queueLength': _queue.length,
'currentResource': _queue.isNotEmpty ? _queue.first.resource : null,
'waitingCount':
_queue.length > (_isPlaying ? 1 : 0)
? _queue.length - (_isPlaying ? 1 : 0)
: 0,
};
}
}
class RoomEntranceWidget extends StatefulWidget {
// 尺寸
final double? width;
final double? height;
// 动画控制参数
final int loops; // 循环次数0表示无限循环
final bool clearsAfterStop;
// 缓存控制
final bool useCache;
// 队列相关参数
final bool useQueue; // 是否使用队列
final int queueDelay; // 队列播放延迟(毫秒)
// 回调函数不再需要onCompleted和onError因为通过队列管理
final VoidCallback? onStartLoading;
final VoidCallback? onFinishLoading;
// 是否在初始化时自动开始监听队列
final bool autoStartQueue;
const RoomEntranceWidget({
Key? key,
this.width,
this.height,
this.loops = 0,
this.clearsAfterStop = true,
this.useCache = true,
this.useQueue = true,
this.queueDelay = 500,
this.onStartLoading,
this.onFinishLoading,
this.autoStartQueue = true,
}) : super(key: key);
@override
_RoomEntranceWidgetState createState() => _RoomEntranceWidgetState();
}
class _RoomEntranceWidgetState extends State<RoomEntranceWidget>
with SingleTickerProviderStateMixin {
SVGAAnimationController? _animationController;
bool _isLoading = false;
bool _hasError = false;
String? _currentResource;
// 用于存储SVGAImage组件的key以便刷新占位符
GlobalKey _svgaKey = GlobalKey();
// 队列管理器实例
final RoomEntranceQueueManager _queueManager = RoomEntranceQueueManager();
// 队列播放相关
VoidCallback? _queueCompletedCallback;
Function(String)? _queueErrorCallback;
// 动态数据
DynamicData? _currentDynamicData;
@override
void initState() {
super.initState();
_animationController = SVGAAnimationController(vsync: this);
// 设置队列延迟
_queueManager.autoPlayDelay = widget.queueDelay;
// 设置当前播放器
_queueManager.setCurrentPlayer(this);
// 添加动画状态监听
_animationController?.addStatusListener((status) {
if (status == AnimationStatus.completed) {
// 如果是在队列播放中,调用队列完成回调
if (_queueCompletedCallback != null) {
_queueCompletedCallback!();
_queueCompletedCallback = null;
_queueErrorCallback = null;
}
}
});
// 如果需要自动开始队列,则检查队列
if (widget.autoStartQueue && widget.useQueue) {
// 延迟一小段时间,确保组件已经挂载
Future.delayed(Duration(milliseconds: 100), () {
_queueManager._startNextAnimation();
});
}
}
// 从队列播放(内部使用)
void playFromQueue({
required String resource,
DynamicData? dynamicData,
VoidCallback? onCompleted,
Function(String)? onError,
}) {
_queueCompletedCallback = onCompleted;
_queueErrorCallback = onError;
_currentDynamicData = dynamicData;
_currentResource = resource;
// 加载动画
_loadAnimation(
resource: resource,
dynamicData: dynamicData,
isFromQueue: true,
);
}
// 加载动画
Future<void> _loadAnimation({
required String resource,
DynamicData? dynamicData,
bool isFromQueue = false,
}) async {
if (widget.onStartLoading != null) {
widget.onStartLoading!();
}
setState(() {
_isLoading = true;
_hasError = false;
});
try {
final isNetworkResource = resource.startsWith('http');
MovieEntity? videoItem;
if (widget.useCache) {
videoItem = SCGiftVapSvgaManager().videoItemCache[resource];
}
if (videoItem == null) {
videoItem =
isNetworkResource
? await SVGAParser.shared.decodeFromURL(resource)
: await SVGAParser.shared.decodeFromAssets(resource);
videoItem.autorelease = false;
if (widget.useCache) {
SCGiftVapSvgaManager().videoItemCache[resource] = videoItem;
}
}
if (mounted) {
// 应用动态数据(文本、图片替换)
await _applyDynamicData(videoItem, dynamicData);
setState(() {
_animationController?.videoItem = videoItem;
_isLoading = false;
});
// 开始播放动画
_startAnimation();
if (widget.onFinishLoading != null) {
widget.onFinishLoading!();
}
}
} catch (e) {
if (mounted) {
setState(() {
_isLoading = false;
_hasError = true;
});
}
// 队列错误回调
if (isFromQueue && _queueErrorCallback != null) {
_queueErrorCallback!(e.toString());
_queueErrorCallback = null;
_queueCompletedCallback = null;
}
// 显示错误信息
SCTts.show("SVGA加载失败: ${e.toString()}");
}
}
// 应用动态数据(文本、图片替换)
Future<void> _applyDynamicData(
MovieEntity videoItem,
DynamicData? dynamicData,
) async {
if (dynamicData == null) return;
// 1. 应用文本替换
if (dynamicData.textReplacements != null) {
await _applyTextReplacements(videoItem, dynamicData.textReplacements!);
}
// 2. 应用图片替换(二进制数据)
if (dynamicData.imageReplacements != null) {
await _applyImageReplacements(videoItem, dynamicData.imageReplacements!);
}
// 3. 应用图片资源路径替换
if (dynamicData.imageAssetPaths != null) {
await _applyImageAssetReplacements(
videoItem,
dynamicData.imageAssetPaths!,
);
}
}
// 应用文本替换
Future<void> _applyTextReplacements(
MovieEntity videoItem,
Map<String, String> textReplacements,
) async {
// 这里需要根据具体的SVGA插件API来替换文本
// 示例代码,具体实现取决于插件支持
if (videoItem.dynamicItem != null) {
textReplacements.forEach((key, value) {
try {
// 根据插件API设置文本
// 注意flutter_svga插件的API可能会有所不同
videoItem.dynamicItem!.setText(
TextPainter(
text: TextSpan(
text: "Hello, World!",
style: TextStyle(
fontSize: 28,
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
key,
);
} catch (e) {
print('设置文本占位符 $key 失败: $e');
}
});
}
}
// 应用图片替换(二进制数据)
Future<void> _applyImageReplacements(
MovieEntity videoItem,
Map<String, Uint8List> imageReplacements,
) async {
// 检查是否支持图片替换
if (videoItem.images != null) {
imageReplacements.forEach((key, imageData) {
try {
// 替换图片数据
// 注意flutter_svga插件的API可能会有所不同
if (videoItem.images!.containsKey(key)) {
videoItem.images![key] = imageData;
}
} catch (e) {
print('设置图片占位符 $key 失败: $e');
}
});
}
}
// 应用图片资源路径替换
Future<void> _applyImageAssetReplacements(
MovieEntity videoItem,
Map<String, String> imageAssetPaths,
) async {
// 如果需要从assets加载图片可以实现这里
// 这通常需要将assets图片转换为Uint8List
}
// 开始播放动画
void _startAnimation() {
_animationController?.reset();
// 根据循环次数设置播放方式
if (widget.loops == 0) {
_animationController?.repeat();
} else {
_animationController?.repeat(count: widget.loops);
}
}
// 播放指定资源(外部调用)
Future<void> playResource({
required String resource,
DynamicData? dynamicData,
}) async {
if (widget.useQueue) {
// 使用队列,添加到队列
final queueItem = AnimationQueueItem(
resource: resource,
dynamicData:
dynamicData != null
? {
'textReplacements': dynamicData.textReplacements,
'imageReplacements': dynamicData.imageReplacements,
'imageAssetPaths': dynamicData.imageAssetPaths,
}
: null,
);
return _queueManager.addToQueue(queueItem);
} else {
// 不使用队列,直接播放
return _loadAnimation(
resource: resource,
dynamicData: dynamicData,
isFromQueue: false,
);
}
}
// 暂停动画
void pause() {
_animationController?.stop();
}
// 停止动画
void stop() {
_animationController?.stop();
if (widget.clearsAfterStop) {
_animationController?.videoItem = null;
setState(() {
_currentResource = null;
});
}
}
// 重新加载当前动画
void reload() {
if (_currentResource != null) {
if (widget.useCache) {
SCGiftVapSvgaManager().videoItemCache.remove(_currentResource);
}
_loadAnimation(
resource: _currentResource!,
dynamicData: _currentDynamicData,
isFromQueue: false,
);
}
}
@override
void dispose() {
stop();
_animationController?.dispose();
// 从队列管理器中移除当前播放器
_queueManager.clearCurrentPlayer();
super.dispose();
}
@override
Widget build(BuildContext context) {
// 显示加载状态
if (_isLoading) {
return Container(
width: widget.width,
height: widget.height,
child: Center(child: CircularProgressIndicator()),
);
}
// 显示错误状态
if (_hasError) {
return Container(
width: widget.width,
height: widget.height,
child: Center(child: Icon(Icons.error, color: Colors.red)),
);
}
// 显示空状态(没有动画时)
if (_animationController?.videoItem == null) {
return Container(
width: widget.width,
height: widget.height,
child: Center(
child: Text('等待动画...', style: TextStyle(color: Colors.grey)),
),
);
}
// 显示SVGA动画
return Container(
width: widget.width,
height: widget.height,
child: SVGAImage(
key: _svgaKey,
_animationController!,
fit: BoxFit.contain,
clearsAfterStop: widget.clearsAfterStop,
),
);
}
}
// 便捷使用的全局方法
class RoomEntranceHelper {
// 添加入场动画到队列
static Future<void> addEntranceAnimation({
required String resource,
Map<String, String>? textReplacements,
Map<String, Uint8List>? imageReplacements,
Map<String, String>? imageAssetPaths,
VoidCallback? onCompleted,
Function(String)? onError,
}) async {
final dynamicData = DynamicData(
textReplacements: textReplacements,
imageReplacements: imageReplacements,
imageAssetPaths: imageAssetPaths,
);
final queueItem = AnimationQueueItem(
resource: resource,
dynamicData: {
'textReplacements': dynamicData.textReplacements,
'imageReplacements': dynamicData.imageReplacements,
'imageAssetPaths': dynamicData.imageAssetPaths,
},
onCompleted: onCompleted,
onError: onError,
);
return RoomEntranceQueueManager().addToQueue(queueItem);
}
// 获取队列状态
static Map<String, dynamic> getQueueStatus() {
return RoomEntranceQueueManager().queueStatus;
}
// 清空队列
static void clearQueue() {
RoomEntranceQueueManager().clearQueue();
}
// 设置队列更新回调
static void setQueueUpdateCallback(void Function() callback) {
RoomEntranceQueueManager().onQueueUpdated = callback;
}
// 设置队列最大长度
static void setMaxQueueLength(int length) {
RoomEntranceQueueManager().maxQueueLength = length;
}
// 设置自动播放延迟
static void setAutoPlayDelay(int delayMs) {
RoomEntranceQueueManager().autoPlayDelay = delayMs;
}
}
// 使用示例
class RoomEntranceExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
// 创建播放器(不需要初始资源)
RoomEntranceWidget(
width: 200,
height: 200,
useQueue: true,
autoStartQueue: true,
),
SizedBox(height: 20),
// 添加动画到队列的按钮
ElevatedButton(
onPressed: () {
RoomEntranceHelper.addEntranceAnimation(
resource: 'assets/animations/entrance.svga',
textReplacements: {'username': '张三', 'level': 'VIP 10'},
onCompleted: () {
print('入场动画播放完成');
},
);
},
child: Text('播放入场动画'),
),
// 添加另一个动画
ElevatedButton(
onPressed: () {
RoomEntranceHelper.addEntranceAnimation(
resource: 'https://example.com/animations/vip_entrance.svga',
textReplacements: {'username': '李四', 'gift_name': '超级火箭'},
);
},
child: Text('播放VIP入场动画'),
),
// 查看队列状态
ElevatedButton(
onPressed: () {
final status = RoomEntranceHelper.getQueueStatus();
print('队列状态: $status');
},
child: Text('查看队列状态'),
),
],
);
}
}