import 'dart:async'; import 'dart:collection'; import 'package:flutter/cupertino.dart'; import 'package:yumi/ui_kit/widgets/room/anim/l_gift_animal_view.dart'; class GiftAnimationManager extends ChangeNotifier { static const int _maxPendingAnimations = 24; static const Duration _activeComboIdleDismissDelay = Duration( milliseconds: 3200, ); Queue pendingAnimationsQueue = Queue(); List animationControllerList = []; //每个控件正在播放的动画 Map giftMap = {0: null, 1: null, 2: null, 3: null}; bool get _controllersReady => animationControllerList.length >= giftMap.length; GiftAnimationSlotSnapshot? slotSnapshotAt(int index) { final gift = giftMap[index]; if (gift == null || animationControllerList.length <= index) { return null; } final bean = animationControllerList[index]; return GiftAnimationSlotSnapshot( index: index, bean: bean, sendUserName: gift.sendUserName, sendToUserName: gift.sendToUserName, sendUserPic: gift.sendUserPic, giftPic: gift.giftPic, giftName: gift.giftName, giftCount: gift.giftCount, rewardAmountText: gift.rewardAmountText, rewardAmount: gift.rewardAmount, showLuckyRewardFrame: gift.showLuckyRewardFrame, labelId: gift.labelId, giftCountStepUnit: gift.giftCountStepUnit, ); } void enqueueGiftAnimation(LGiftModel giftModel) { if (_mergeIntoActiveAnimation(giftModel)) { return; } if (_mergeIntoPendingAnimation(giftModel)) { return; } _trimPendingAnimations(); pendingAnimationsQueue.add(giftModel); proceedToNextAnimation(); } void _trimPendingAnimations() { while (pendingAnimationsQueue.length >= _maxPendingAnimations) { pendingAnimationsQueue.removeFirst(); } } bool _mergeIntoActiveAnimation(LGiftModel incoming) { for (final entry in giftMap.entries) { final current = entry.value; if (current == null || current.labelId != incoming.labelId) { continue; } _mergeGiftModel(target: current, incoming: incoming); notifyListeners(); _refreshSlotAnimation(entry.key, restartEntry: false); return true; } return false; } bool _mergeIntoPendingAnimation(LGiftModel incoming) { for (final pending in pendingAnimationsQueue) { if (pending.labelId != incoming.labelId) { continue; } _mergeGiftModel(target: pending, incoming: incoming); return true; } return false; } void _mergeGiftModel({ required LGiftModel target, required LGiftModel incoming, }) { target.giftCount = (target.giftCount) + (incoming.giftCount); if (target.sendUserName.isEmpty) { target.sendUserName = incoming.sendUserName; } if (target.sendToUserName.isEmpty) { target.sendToUserName = incoming.sendToUserName; } if (target.sendUserPic.isEmpty) { target.sendUserPic = incoming.sendUserPic; } if (target.giftPic.isEmpty) { target.giftPic = incoming.giftPic; } if (target.giftName.isEmpty) { target.giftName = incoming.giftName; } target.rewardAmount = target.rewardAmount + incoming.rewardAmount; if (incoming.rewardAmountText.isNotEmpty) { target.rewardAmountText = incoming.rewardAmountText; } target.showLuckyRewardFrame = target.showLuckyRewardFrame || incoming.showLuckyRewardFrame; if (incoming.giftCountStepUnit > 0) { target.giftCountStepUnit = incoming.giftCountStepUnit; } } ///开始播放 proceedToNextAnimation() { if (pendingAnimationsQueue.isEmpty || !_controllersReady) { return; } var playGift = pendingAnimationsQueue.first; for (var key in giftMap.keys) { var value = giftMap[key]; if (value == null) { giftMap[key] = playGift; pendingAnimationsQueue.removeFirst(); notifyListeners(); _refreshSlotAnimation(key, restartEntry: true); break; } else { if (value.labelId == playGift.labelId) { _mergeGiftModel(target: value, incoming: playGift); pendingAnimationsQueue.removeFirst(); notifyListeners(); _refreshSlotAnimation(key, restartEntry: false); break; } } } } void attachAnimationControllers(List anins) { animationControllerList = anins; proceedToNextAnimation(); } void cleanupAnimationResources() { pendingAnimationsQueue.clear(); giftMap[0] = null; giftMap[1] = null; giftMap[2] = null; giftMap[3] = null; for (var element in animationControllerList) { element.dismissTimer?.cancel(); element.controller.dispose(); element.countPulseController.dispose(); } animationControllerList.clear(); } void markAnimationAsFinished(int index) { _cancelSlotDismissTimer(index); if (giftMap[index] == null) { return; } giftMap[index] = null; notifyListeners(); proceedToNextAnimation(); } void _refreshSlotAnimation(int index, {required bool restartEntry}) { if (animationControllerList.length <= index) { return; } final bean = animationControllerList[index]; _restartSlotDismissTimer(index); bean.countPulseController.forward(from: 0); if (restartEntry) { bean.controller.forward(from: 0); } } void _restartSlotDismissTimer(int index) { if (animationControllerList.length <= index) { return; } final bean = animationControllerList[index]; bean.dismissTimer?.cancel(); bean.dismissTimer = Timer(_activeComboIdleDismissDelay, () { if (giftMap[index] == null) { return; } markAnimationAsFinished(index); }); } void _cancelSlotDismissTimer(int index) { if (animationControllerList.length <= index) { return; } animationControllerList[index].dismissTimer?.cancel(); animationControllerList[index].dismissTimer = null; } } @immutable class GiftAnimationSlotSnapshot { const GiftAnimationSlotSnapshot({ required this.index, required this.bean, required this.sendUserName, required this.sendToUserName, required this.sendUserPic, required this.giftPic, required this.giftName, required this.giftCount, required this.rewardAmountText, required this.rewardAmount, required this.showLuckyRewardFrame, required this.labelId, required this.giftCountStepUnit, }); final int index; final LGiftScrollingScreenAnimsBean bean; final String sendUserName; final String sendToUserName; final String sendUserPic; final String giftPic; final String giftName; final num giftCount; final String rewardAmountText; final num rewardAmount; final bool showLuckyRewardFrame; final String labelId; final num giftCountStepUnit; @override bool operator ==(Object other) { if (identical(this, other)) { return true; } return other is GiftAnimationSlotSnapshot && other.index == index && identical(other.bean, bean) && other.sendUserName == sendUserName && other.sendToUserName == sendToUserName && other.sendUserPic == sendUserPic && other.giftPic == giftPic && other.giftName == giftName && other.giftCount == giftCount && other.rewardAmountText == rewardAmountText && other.rewardAmount == rewardAmount && other.showLuckyRewardFrame == showLuckyRewardFrame && other.labelId == labelId && other.giftCountStepUnit == giftCountStepUnit; } @override int get hashCode => Object.hash( index, bean, sendUserName, sendToUserName, sendUserPic, giftPic, giftName, giftCount, rewardAmountText, rewardAmount, showLuckyRewardFrame, labelId, giftCountStepUnit, ); }