Browse Source
feat(discover): 实现SVGA动画播放管理器和播放组件
feat(discover): 实现SVGA动画播放管理器和播放组件
- 新增 SvgaPlayerManager 管理SVGA动画播放队列 - 创建 SvgaPlayerWidget 用于实际播放SVGA动画 - 支持本地assets和网络URL两种SVGA文件加载方式 - 实现动画播放完成和错误处理回调机制 - 提供队列控制方法如添加、清空、停止播放等功能 - 修复房间控制器中SVGA播放器导入路径错误问题ios
3 changed files with 191 additions and 1 deletions
Unified View
Diff Options
-
2lib/controller/discover/room_controller.dart
-
86lib/controller/discover/svga_player_manager.dart
-
104lib/widget/live/svga_player_widget.dart
@ -0,0 +1,86 @@ |
|||||
|
import 'dart:collection'; |
||||
|
import 'package:flutter/material.dart'; |
||||
|
import 'package:flutter_svga/flutter_svga.dart'; |
||||
|
import 'package:get/get.dart'; |
||||
|
|
||||
|
/// SVGA 动画项 |
||||
|
class SvgaAnimationItem { |
||||
|
final String svgaFile; |
||||
|
final String? targetUserId; // 接收礼物的用户ID |
||||
|
final String? senderUserId; // 发送礼物的用户ID |
||||
|
final String? giftProductId; // 礼物产品ID |
||||
|
|
||||
|
SvgaAnimationItem({ |
||||
|
required this.svgaFile, |
||||
|
this.targetUserId, |
||||
|
this.senderUserId, |
||||
|
this.giftProductId, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/// SVGA 动画播放队列管理器 |
||||
|
/// 注意:由于 SVGAAnimationController 需要 vsync,实际播放需要在 Widget 中完成 |
||||
|
/// 这个管理器只负责管理队列,实际的播放需要通过回调通知外部 Widget |
||||
|
class SvgaPlayerManager extends GetxController { |
||||
|
static SvgaPlayerManager? _instance; |
||||
|
static SvgaPlayerManager get instance { |
||||
|
_instance ??= Get.put(SvgaPlayerManager()); |
||||
|
return _instance!; |
||||
|
} |
||||
|
|
||||
|
final Queue<SvgaAnimationItem> _animationQueue = Queue<SvgaAnimationItem>(); |
||||
|
final Rx<SvgaAnimationItem?> currentItem = Rx<SvgaAnimationItem?>(null); |
||||
|
final RxBool isPlaying = false.obs; |
||||
|
|
||||
|
/// 添加动画到队列 |
||||
|
void addToQueue(SvgaAnimationItem item) { |
||||
|
_animationQueue.add(item); |
||||
|
_playNext(); |
||||
|
} |
||||
|
|
||||
|
/// 播放下一个动画 |
||||
|
void _playNext() { |
||||
|
if (isPlaying.value || _animationQueue.isEmpty) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
final item = _animationQueue.removeFirst(); |
||||
|
currentItem.value = item; |
||||
|
isPlaying.value = true; |
||||
|
print('✅ SVGA 动画已添加到播放队列: ${item.svgaFile}'); |
||||
|
} |
||||
|
|
||||
|
/// 标记当前动画播放完成 |
||||
|
void onAnimationFinished() { |
||||
|
print('✅ SVGA 动画播放完成'); |
||||
|
isPlaying.value = false; |
||||
|
currentItem.value = null; |
||||
|
// 播放下一个 |
||||
|
_playNext(); |
||||
|
} |
||||
|
|
||||
|
/// 标记当前动画播放失败 |
||||
|
void onAnimationError(String error) { |
||||
|
print('❌ SVGA 动画播放失败: $error'); |
||||
|
isPlaying.value = false; |
||||
|
currentItem.value = null; |
||||
|
// 继续播放下一个 |
||||
|
_playNext(); |
||||
|
} |
||||
|
|
||||
|
/// 停止当前播放 |
||||
|
void stop() { |
||||
|
isPlaying.value = false; |
||||
|
currentItem.value = null; |
||||
|
} |
||||
|
|
||||
|
/// 清空队列 |
||||
|
void clearQueue() { |
||||
|
stop(); |
||||
|
_animationQueue.clear(); |
||||
|
} |
||||
|
|
||||
|
/// 获取队列长度 |
||||
|
int get queueLength => _animationQueue.length; |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,104 @@ |
|||||
|
import 'package:flutter/material.dart'; |
||||
|
import 'package:flutter_svga/flutter_svga.dart'; |
||||
|
import 'package:get/get.dart'; |
||||
|
import 'package:dating_touchme_app/controller/discover/svga_player_manager.dart'; |
||||
|
|
||||
|
/// SVGA 动画播放 Widget |
||||
|
/// 监听 SvgaPlayerManager 的队列,自动播放 SVGA 动画 |
||||
|
class SvgaPlayerWidget extends StatefulWidget { |
||||
|
const SvgaPlayerWidget({super.key}); |
||||
|
|
||||
|
@override |
||||
|
State<SvgaPlayerWidget> createState() => _SvgaPlayerWidgetState(); |
||||
|
} |
||||
|
|
||||
|
class _SvgaPlayerWidgetState extends State<SvgaPlayerWidget> |
||||
|
with SingleTickerProviderStateMixin { |
||||
|
final SvgaPlayerManager _manager = SvgaPlayerManager.instance; |
||||
|
SVGAAnimationController? _controller; |
||||
|
SvgaAnimationItem? _currentItem; |
||||
|
|
||||
|
@override |
||||
|
void initState() { |
||||
|
super.initState(); |
||||
|
// 监听队列变化 |
||||
|
ever(_manager.currentItem, (item) { |
||||
|
if (item != null && item != _currentItem) { |
||||
|
_playAnimation(item); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
void dispose() { |
||||
|
_controller?.dispose(); |
||||
|
super.dispose(); |
||||
|
} |
||||
|
|
||||
|
/// 播放动画 |
||||
|
Future<void> _playAnimation(SvgaAnimationItem item) async { |
||||
|
// 如果正在播放,先停止 |
||||
|
if (_controller != null) { |
||||
|
_controller!.dispose(); |
||||
|
_controller = null; |
||||
|
} |
||||
|
|
||||
|
_currentItem = item; |
||||
|
_controller = SVGAAnimationController(vsync: this); |
||||
|
|
||||
|
try { |
||||
|
// 判断是网络 URL 还是本地资源 |
||||
|
if (item.svgaFile.startsWith('http://') || |
||||
|
item.svgaFile.startsWith('https://')) { |
||||
|
// 网络 URL |
||||
|
SVGAParser.shared.decodeFromURL(item.svgaFile).then((video) { |
||||
|
if (mounted && _currentItem == item) { |
||||
|
_controller!.videoItem = video; |
||||
|
_controller!.repeat(); |
||||
|
print('✅ SVGA 动画加载成功(网络): ${item.svgaFile}'); |
||||
|
} |
||||
|
}).catchError((error) { |
||||
|
print('❌ SVGA 动画加载失败(网络): $error'); |
||||
|
_manager.onAnimationError(error.toString()); |
||||
|
}); |
||||
|
} else { |
||||
|
// 本地资源(assets) |
||||
|
SVGAParser.shared.decodeFromAssets(item.svgaFile).then((video) { |
||||
|
if (mounted && _currentItem == item) { |
||||
|
_controller!.videoItem = video; |
||||
|
_controller!.repeat(); |
||||
|
print('✅ SVGA 动画加载成功(本地): ${item.svgaFile}'); |
||||
|
} |
||||
|
}).catchError((error) { |
||||
|
print('❌ SVGA 动画加载失败(本地): $error'); |
||||
|
_manager.onAnimationError(error.toString()); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// 监听动画完成(repeat 模式不会自动完成,需要手动停止) |
||||
|
// 这里可以根据需要调整播放逻辑 |
||||
|
} catch (e) { |
||||
|
print('❌ SVGA 播放异常: $e'); |
||||
|
_manager.onAnimationError(e.toString()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
Widget build(BuildContext context) { |
||||
|
return Obx(() { |
||||
|
final currentItem = _manager.currentItem.value; |
||||
|
final isPlaying = _manager.isPlaying.value; |
||||
|
|
||||
|
if (!isPlaying || currentItem == null || _controller == null) { |
||||
|
return const SizedBox.shrink(); |
||||
|
} |
||||
|
|
||||
|
return Positioned.fill( |
||||
|
child: IgnorePointer( |
||||
|
child: SVGAImage(_controller!), |
||||
|
), |
||||
|
); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
Write
Preview
Loading…
Cancel
Save