在语聊房项目中,礼物特效播放是一个常见的需求,通常包括动画、声音等多种媒体形式。为了处理不同的礼物类型,我们可以采用抽象的设计方法,使得系统易于扩展和维护。
设计架构思路:
抽象礼物特效接口:定义一个通用的礼物特效接口,所有类型的礼物特效都实现这个接口。接口中包括播放、停止、释放资源等方法。
礼物特效工厂:使用工厂模式根据礼物类型创建对应的特效实例。
特效管理:有一个特效管理器来管理当前正在播放的特效,处理多个礼物同时播放时的排队、叠加等逻辑。
资源管理:礼物特效可能包含动画、声音等资源,需要统一管理资源的加载和释放。
与UI层解耦:特效播放器应该与UI层解耦,通过事件或回调与UI通信。
一、架构设计
二、核心抽象设计
1. 礼物特效抽象基类
abstract class GiftEffect {final String effectId;final int priority; // 特效优先级,用于处理多个特效同时播放的情况final Duration duration; // 特效持续时间GiftEffect({required this.effectId,required this.priority,required this.duration,});// 初始化特效资源Future<void> initialize();// 播放特效Future<void> play();// 停止特效Future<void> stop();// 释放资源Future<void> dispose();// 特效状态变化回调void Function(GiftEffectState state)? onStateChanged;
}// 特效状态枚举
enum GiftEffectState {initializing,ready,playing,paused,completed,error,
}
2. 不同礼物类型的特效实现
基础礼物特效
class BasicGiftEffect extends GiftEffect {final String animationPath;final String? soundPath;AnimationController? _animationController;AudioPlayer? _audioPlayer;BasicGiftEffect({required super.effectId,required super.priority,required super.duration,required this.animationPath,this.soundPath,});@overrideFuture<void> initialize() async {try {// 预加载动画资源await precacheAnimation(animationPath);// 如果有音效,预加载音效if (soundPath != null) {_audioPlayer = AudioPlayer();await _audioPlayer!.setAsset(soundPath!);}onStateChanged?.call(GiftEffectState.ready);} catch (e) {onStateChanged?.call(GiftEffectState.error);rethrow;}}@overrideFuture<void> play() async {onStateChanged?.call(GiftEffectState.playing);// 播放动画_animationController = AnimationController(duration: duration,vsync: // 获取TickerProvider,)..forward();// 播放音效if (_audioPlayer != null) {await _audioPlayer!.play();}// 监听动画完成_animationController!.addStatusListener((status) {if (status == AnimationStatus.completed) {onStateChanged?.call(GiftEffectState.completed);}});}@overrideFuture<void> stop() async {_animationController?.stop();await _audioPlayer?.stop();onStateChanged?.call(GiftEffectState.paused);}@overrideFuture<void> dispose() async {_animationController?.dispose();await _audioPlayer?.dispose();onStateChanged?.call(GiftEffectState.completed);}
}
高级礼物特效(如全屏特效)
class FullScreenGiftEffect extends GiftEffect {final String lottieAnimationPath;final String particleEffectPath;final String backgroundMusicPath;final List<String> additionalEffects;FullScreenGiftEffect({required super.effectId,required super.priority,required super.duration,required this.lottieAnimationPath,required this.particleEffectPath,required this.backgroundMusicPath,this.additionalEffects = const [],});@overrideFuture<void> initialize() async {// 使用Isolate预加载复杂资源,避免阻塞UI线程await compute(_preloadFullScreenResources, {'lottie': lottieAnimationPath,'particles': particleEffectPath,'music': backgroundMusicPath,'additional': additionalEffects,});onStateChanged?.call(GiftEffectState.ready);}static void _preloadFullScreenResources(Map<String, dynamic> resources) {// 在Isolate中执行资源预加载// 这里可以加载Lottie动画、粒子效果、音乐等}@overrideFuture<void> play() async {// 实现全屏特效播放逻辑// 可能涉及多个动画同步、粒子系统、音乐播放等}// 其他方法实现...
}
3D礼物特效
class ThreeDGiftEffect extends GiftEffect {final String modelPath;final String animationName;final List<LightConfig> lights;final CameraConfig camera;ThreeDGiftEffect({required super.effectId,required super.priority,required super.duration,required this.modelPath,required this.animationName,required this.lights,required this.camera,});@overrideFuture<void> initialize() async {// 使用Isolate加载3D模型和动画await compute(_load3DModel, modelPath);onStateChanged?.call(GiftEffectState.ready);}static void _load3DModel(String modelPath) {// 在Isolate中加载3D模型}@overrideFuture<void> play() async {// 实现3D特效播放逻辑// 使用Flutter 3D渲染引擎(如Filament或自定义OpenGL渲染)}// 其他方法实现...
}
3. 礼物特效工厂
class GiftEffectFactory {static GiftEffect createEffect(GiftItem gift) {switch (gift.type) {case GiftType.basic:return BasicGiftEffect(effectId: gift.id,priority: gift.priority,duration: gift.duration,animationPath: gift.animationPath,soundPath: gift.soundPath,);case GiftType.fullScreen:return FullScreenGiftEffect(effectId: gift.id,priority: gift.priority,duration: gift.duration,lottieAnimationPath: gift.animationPath,particleEffectPath: gift.particleEffectPath,backgroundMusicPath: gift.backgroundMusicPath,additionalEffects: gift.additionalEffects,);case GiftType.threeD:return ThreeDGiftEffect(effectId: gift.id,priority: gift.priority,duration: gift.duration,modelPath: gift.modelPath,animationName: gift.animationName,lights: gift.lights,camera: gift.camera,);case GiftType.special:// 特殊礼物类型的处理return SpecialGiftEffect(effectId: gift.id,priority: gift.priority,duration: gift.duration,// 特殊参数...);default:throw Exception('Unsupported gift type: ${gift.type}');}}
}
4. 礼物特效管理器
class GiftEffectManager {final List<GiftEffect> _activeEffects = [];final Queue<GiftEffect> _effectQueue = Queue();final int _maxConcurrentEffects;GiftEffectManager({int maxConcurrentEffects = 3}): _maxConcurrentEffects = maxConcurrentEffects;// 添加礼物特效到播放队列Future<void> addGiftEffect(GiftItem gift) async {final effect = GiftEffectFactory.createEffect(gift);// 初始化特效await effect.initialize();// 监听状态变化effect.onStateChanged = (state) {if (state == GiftEffectState.completed || state == GiftEffectState.error) {_removeEffect(effect);_playNextEffect();}};// 根据优先级决定是立即播放还是加入队列if (_activeEffects.length < _maxConcurrentEffects) {_activeEffects.add(effect);await effect.play();} else {// 查找队列中是否有更低优先级的特效可以替换final lowestPriorityEffect = _findLowestPriorityEffect();if (lowestPriorityEffect != null && effect.priority > lowestPriorityEffect.priority) {// 暂停低优先级特效,播放高优先级特效await lowestPriorityEffect.stop();_activeEffects.remove(lowestPriorityEffect);_effectQueue.addFirst(lowestPriorityEffect);_activeEffects.add(effect);await effect.play();} else {// 加入队列等待_effectQueue.add(effect);}}}// 查找当前播放中优先级最低的特效GiftEffect? _findLowestPriorityEffect() {if (_activeEffects.isEmpty) return null;GiftEffect lowest = _activeEffects.first;for (final effect in _activeEffects) {if (effect.priority < lowest.priority) {lowest = effect;}}return lowest;}// 移除特效void _removeEffect(GiftEffect effect) {_activeEffects.remove(effect);effect.dispose();}// 播放下一个队列中的特效void _playNextEffect() {if (_effectQueue.isNotEmpty && _activeEffects.length < _maxConcurrentEffects) {final nextEffect = _effectQueue.removeFirst();_activeEffects.add(nextEffect);nextEffect.play();}}// 清空所有特效Future<void> clearAllEffects() async {for (final effect in _activeEffects) {await effect.stop();await effect.dispose();}_activeEffects.clear();for (final effect in _effectQueue) {await effect.dispose();}_effectQueue.clear();}
}
5. 资源加载与缓存
class EffectResourceLoader {static final Map<String, dynamic> _resourceCache = {};// 预加载常用礼物资源static Future<void> preloadCommonGiftResources(List<String> giftIds) async {await Future.wait(giftIds.map((id) => _preloadGiftResources(id)));}static Future<void> _preloadGiftResources(String giftId) async {// 获取礼物配置信息final giftConfig = await GiftConfigService.getConfig(giftId);// 使用Isolate加载资源,避免阻塞UI线程final resources = await compute(_loadResourcesInIsolate, giftConfig);// 缓存资源_resourceCache[giftId] = resources;}static Future<Map<String, dynamic>> _loadResourcesInIsolate(GiftConfig config) async {final resources = <String, dynamic>{};// 加载动画资源if (config.animationPath != null) {resources['animation'] = await loadAnimation(config.animationPath!);}// 加载音效资源if (config.soundPath != null) {resources['sound'] = await loadAudio(config.soundPath!);}// 加载其他资源...return resources;}// 获取已缓存的资源static dynamic getCachedResource(String giftId, String resourceType) {final giftResources = _resourceCache[giftId];return giftResources != null ? giftResources[resourceType] : null;}// 清理缓存static void clearCache() {_resourceCache.clear();}
}
三、性能优化策略
1. 资源预加载与缓存
在应用启动时预加载常用礼物资源
实现LRU缓存策略,自动清理不常用的资源
根据用户行为预测下一步可能发送的礼物,提前加载
2. Isolate的使用
使用Isolate进行资源加载,避免阻塞UI线程
复杂特效的渲染在单独的Isolate中进行
实现Isolate池管理,复用Isolate实例
3. 内存管理
实现特效资源的引用计数,及时释放不再使用的资源
监控内存使用情况,在内存紧张时自动降低特效质量或跳过次要特效
4. 特效优先级与队列管理
根据礼物价值、发送者身份等因素动态调整特效优先级
实现智能队列管理,避免低优先级特效阻塞高优先级特效
总结
这个礼物特效播放架构通过抽象工厂模式创建不同类型的特效,使用管理器处理特效的优先级和并发播放,利用Isolate进行资源加载和复杂计算,实现了高性能、可扩展的礼物特效系统。该架构具有以下优点:
良好的扩展性:通过抽象接口,可以轻松添加新的礼物特效类型
高性能:使用Isolate进行资源加载和复杂计算,避免阻塞UI线程
智能调度:基于优先级的特效队列管理,确保重要特效优先播放
内存友好:实现了资源缓存和内存管理机制,避免内存泄漏
易于维护:分层架构和清晰的职责划分,使代码易于理解和维护
这种设计能够满足语聊房项目中各种复杂礼物特效的需求,同时保证应用的流畅性和稳定性。