在语聊房项目中,礼物特效播放是一个常见的需求,通常包括动画、声音等多种媒体形式。为了处理不同的礼物类型,我们可以采用抽象的设计方法,使得系统易于扩展和维护。
设计架构思路:
抽象礼物特效接口:定义一个通用的礼物特效接口,所有类型的礼物特效都实现这个接口。接口中包括播放、停止、释放资源等方法。
礼物特效工厂:使用工厂模式根据礼物类型创建对应的特效实例。
特效管理:有一个特效管理器来管理当前正在播放的特效,处理多个礼物同时播放时的排队、叠加等逻辑。
资源管理:礼物特效可能包含动画、声音等资源,需要统一管理资源的加载和释放。
与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,
});
@override
Future<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;
}
}
@override
Future<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);
}
});
}
@override
Future<void> stop() async {
_animationController?.stop();
await _audioPlayer?.stop();
onStateChanged?.call(GiftEffectState.paused);
}
@override
Future<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 [],
});
@override
Future<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动画、粒子效果、音乐等
}
@override
Future<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,
});
@override
Future<void> initialize() async {
// 使用Isolate加载3D模型和动画
await compute(_load3DModel, modelPath);
onStateChanged?.call(GiftEffectState.ready);
}
static void _load3DModel(String modelPath) {
// 在Isolate中加载3D模型
}
@override
Future<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线程
智能调度:基于优先级的特效队列管理,确保重要特效优先播放
内存友好:实现了资源缓存和内存管理机制,避免内存泄漏
易于维护:分层架构和清晰的职责划分,使代码易于理解和维护
这种设计能够满足语聊房项目中各种复杂礼物特效的需求,同时保证应用的流畅性和稳定性。