這個是從已上線半年的直播項目中抽出來的禮物模塊,通過壓測無任何問題钳宪。
準(zhǔn)備1. 生成禮物模型
@interface NDGiftModel : NSObject
@property (nonatomic, strong) NDGifts *gift;
@property (nonatomic, strong) NDUserModel *user;
/** 禮物操作的唯一Key 由用戶ID+禮物ID生成 */
@property (nonatomic, copy) NSString *giftKey;
// 氣泡動畫顯示時間 秒
@property (nonatomic, assign) CGFloat time;
// 單次收到禮物的數(shù)量
@property (nonatomic, assign) NSInteger giftCount;
// 禮物連擊上限
@property (nonatomic, assign) NSInteger doubleHitCount;
@end
準(zhǔn)備2. 了解我們的禮物動畫運行過程
/** 動畫過程
這是一個普遍的禮物動畫過程晾匠,
當(dāng)然你可以根據(jù)自身業(yè)務(wù)調(diào)整
*/
typedef NS_ENUM(NSInteger, NDAnimationStatus) {
NDAnimationStatusUnknown = 0,
NDAnimationStatusStart, // 開始運行動畫,從左邊橫向出現(xiàn)的動畫(0.2s)
NDAnimationStatusSerial, // 連擊動畫中~美浦,一個放大縮小動畫(0.3s)
NDAnimationStatusStop, // 動畫已停止输莺,懸浮在視圖上(默認(rèn)2秒戚哎,可根據(jù)??調(diào)整時間)
NDAnimationStatusEnd, // 動畫將結(jié)束,視圖向上的漸隱消失動畫(0.2s)
};
初始化我們的禮物管理器
- (instancetype)initWithView:(UIView *)bearView {
if (self = [super init]) {
// 沒有做屏幕適配嫂用,可自行調(diào)整
CGFloat _width = 260;
CGFloat _maxY = [UIScreen mainScreen].bounds.size.height / 2 - 48;
for (int i = 0; i<2; i++) {
NDGiftAnimationView *animationV = [[NDGiftAnimationView alloc] init];
if (i == 1) {
animationV.frame = CGRectMake(-_width, _maxY, _width, 40);
} else {
animationV.frame = CGRectMake(-_width, 40+8+_maxY, _width, 40);
}
[bearView addSubview:animationV];
[self.animationArray addObject:animationV];
}
}
return self;
}
1. 客戶端收到禮物
// 收到禮物
- (void)receivedGift:(NDGiftModel *)gift {
if (!gift) return;
// 更新總數(shù)量
gift.doubleHitCount = gift.giftCount;
// 1. 判讀當(dāng)前禮物視圖是否需要顯示動畫
for (NDGiftAnimationView *giftView in self.animationArray) {
BOOL update = [giftView animationStatusWith:gift];
if (update) {
return;
}
}
// 2. 追加|更新禮物隊列
[self insertOrUpdate:gift];
// 3. 執(zhí)行禮物隊列動畫
[self animateNextGift];
}
1.1 根據(jù)禮物視圖狀態(tài)決定接下來的操作
- (BOOL)animationStatusWith:(NDGiftModel *)gift {
// 是否同用戶同禮物 判斷唯一的key
if ([self.currentGift.giftKey isEqualToString:gift.giftKey]) {
// 禮物即將結(jié)束或者處于未啟動狀態(tài)
if (self.animationStatus == NDAnimationStatusUnknown || self.animationStatus == NDAnimationStatusEnd) {
return NO;
}
// 禮物處于開始動畫中
if (self.animationStatus == NDAnimationStatusStart) {
self.currentGift.giftCount = gift.giftCount;
self.currentGift.doubleHitCount += self.currentGift.giftCount;
return YES;
}
// 禮物處于連擊狀態(tài)
if (self.animationStatus == NDAnimationStatusSerial) {
self.currentGift.giftCount = gift.giftCount;
self.currentGift.doubleHitCount += self.currentGift.giftCount;
return YES;
}
// 禮物停止運行動畫型凳,處于停止中
if (self.animationStatus == NDAnimationStatusStop) {
self.currentGift.giftCount = gift.giftCount;
self.currentGift.doubleHitCount += self.currentGift.giftCount;
// 連擊
[self doShakeNumberLabel];
return YES;
}
}
return NO;
}
從上面的禮物視圖狀態(tài):
- 當(dāng)動畫未開始和即將結(jié)束我們直接返回 NO,
- 動畫即將開始了嘱函,這個時候我們又接收到同樣的禮物我們只需要更新數(shù)量就可以了
- 動畫處于連擊狀態(tài)中甘畅,同樣的只需要更新禮物數(shù)量
- 動畫處于懸浮在頁面上,動畫也停止了实夹,這個時候我們需要更新數(shù)量橄浓,并且從新啟動連擊動畫。
1.2 動畫開始中~~
- (void)startAnimationWithGift:(NDGiftModel *)gift finishedBlock:(void (^)(NDGiftModel * _Nonnull))finishedBlock {
self.animationStatus = NDAnimationStatusStart;
self.currentGift = gift;
self.finishedBlock = finishedBlock;
self.originFrame = self.frame;
NDWeakSelf
[UIView animateWithDuration:AnimationStartDuration animations:^{
weakSelf.alpha = 1.0;
// 該動畫是將X軸設(shè)置為0 橫向移動效果
weakSelf.x = 0;
} completion:^(BOOL finished) {
[weakSelf doShakeNumberLabel];
}];
}
1.3 動畫連擊中
- (void)doShakeNumberLabel {
[self cleanDelayedBlockHandle];
self.animationStatus = NDAnimationStatusSerial;
_currentIndex = self.currentGift.doubleHitCount;
self.animationImgView.showCount = _currentIndex;
NDWeakSelf;
[self.animationImgView startAnimWithDuration:AnimationSerialDuration complate:^{
// 判斷禮物連擊是否達(dá)到上限
if (weakSelf.currentIndex >= weakSelf.currentGift.doubleHitCount) {
// 更新禮物狀態(tài)處于靜止中
weakSelf.animationStatus = NDAnimationStatusStop;
weakSelf.delayedBlockHandle = perform_block_after_delay(weakSelf.timeFloat, ^{
[weakSelf endAnimation];
});
} else { // 遞歸 繼續(xù)連擊
[weakSelf doShakeNumberLabel];
}
}];
}
1.4 動畫即將結(jié)束
- (void)endAnimation {
self.animationStatus = NDAnimationStatusEnd;
NDWeakSelf;
// 該動畫是向上移動一小段距離的效果 隱藏
[UIView animateWithDuration:AnimationEndDuration delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
weakSelf.y -= 10;
weakSelf.alpha = 0.0; // 漸變逐漸隱藏
} completion:^(BOOL finished) {
weakSelf.frame = weakSelf.originFrame;
weakSelf.alpha = 0.0;
weakSelf.animationStatus = NDAnimationStatusUnknown;
weakSelf.currentGift = nil;
if (weakSelf.finishedBlock) {
weakSelf.finishedBlock(weakSelf.currentGift);
}
}];
}
2. 追加|更新禮物隊列
- (void)insertOrUpdate:(NDGiftModel *)model {
// 遍歷相同禮物累加
for (NDGiftModel *item in self.giftArray) {
if ([item.giftKey isEqualToString:model.giftKey]) {
item.giftCount = model.giftCount;
item.doubleHitCount += item.giftCount;
return;
}
}
// 優(yōu)先級插入(價格高的在前)
// [obj.gift.contributions floatValue] < [model.gift.contributions floatValue])
[self.giftArray addObject:model];
}
3. 執(zhí)行禮物隊列動畫
/** 執(zhí)行禮物動畫 */
- (void)animateNextGift {
// 1. 沒有要顯示的禮物
NDGiftModel *gift = self.giftArray.firstObject;
if (!gift) {
return;
}
// 2. 執(zhí)行禮物動畫
NDWeakSelf;
for (NDGiftAnimationView *animationView in self.animationArray) {
if (animationView.animationStatus == NDAnimationStatusUnknown) {
[weakSelf.giftArray removeObject:gift];
[animationView startAnimationWithGift:gift finishedBlock:^(NDGiftModel *gift) {
// 執(zhí)行完動畫遞歸
[weakSelf animateNextGift];
}];
return;
}
}
}