这个是从已上线半年的直播项目中抽出来的礼物模块,通过压测无任何问题。
准备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;
// 单次收到礼物的数量
@property (nonatomic, assign) NSInteger giftCount;
// 礼物连击上限
@property (nonatomic, assign) NSInteger doubleHitCount;
@end
准备2. 了解我们的礼物动画运行过程
/** 动画过程
这是一个普遍的礼物动画过程,
当然你可以根据自身业务调整
*/
typedef NS_ENUM(NSInteger, NDAnimationStatus) {
NDAnimationStatusUnknown = 0,
NDAnimationStatusStart, // 开始运行动画,从左边横向出现的动画(0.2s)
NDAnimationStatusSerial, // 连击动画中~,一个放大缩小动画(0.3s)
NDAnimationStatusStop, // 动画已停止,悬浮在视图上(默认2秒,可根据🎁调整时间)
NDAnimationStatusEnd, // 动画将结束,视图向上的渐隐消失动画(0.2s)
};
初始化我们的礼物管理器
- (instancetype)initWithView:(UIView *)bearView {
if (self = [super init]) {
// 没有做屏幕适配,可自行调整
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;
// 更新总数量
gift.doubleHitCount = gift.giftCount;
// 1. 判读当前礼物视图是否需要显示动画
for (NDGiftAnimationView *giftView in self.animationArray) {
BOOL update = [giftView animationStatusWith:gift];
if (update) {
return;
}
}
// 2. 追加|更新礼物队列
[self insertOrUpdate:gift];
// 3. 执行礼物队列动画
[self animateNextGift];
}
1.1 根据礼物视图状态决定接下来的操作
- (BOOL)animationStatusWith:(NDGiftModel *)gift {
// 是否同用户同礼物 判断唯一的key
if ([self.currentGift.giftKey isEqualToString:gift.giftKey]) {
// 礼物即将结束或者处于未启动状态
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;
}
// 礼物处于连击状态
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;
}
从上面的礼物视图状态:
- 当动画未开始和即将结束我们直接返回 NO,
- 动画即将开始了,这个时候我们又接收到同样的礼物我们只需要更新数量就可以了
- 动画处于连击状态中,同样的只需要更新礼物数量
- 动画处于悬浮在页面上,动画也停止了,这个时候我们需要更新数量,并且从新启动连击动画。
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轴设置为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:^{
// 判断礼物连击是否达到上限
if (weakSelf.currentIndex >= weakSelf.currentGift.doubleHitCount) {
// 更新礼物状态处于静止中
weakSelf.animationStatus = NDAnimationStatusStop;
weakSelf.delayedBlockHandle = perform_block_after_delay(weakSelf.timeFloat, ^{
[weakSelf endAnimation];
});
} else { // 递归 继续连击
[weakSelf doShakeNumberLabel];
}
}];
}
1.4 动画即将结束
- (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;
}
}
// 优先级插入(价格高的在前)
// [obj.gift.contributions floatValue] < [model.gift.contributions floatValue])
[self.giftArray addObject:model];
}
3. 执行礼物队列动画
/** 执行礼物动画 */
- (void)animateNextGift {
// 1. 没有要显示的礼物
NDGiftModel *gift = self.giftArray.firstObject;
if (!gift) {
return;
}
// 2. 执行礼物动画
NDWeakSelf;
for (NDGiftAnimationView *animationView in self.animationArray) {
if (animationView.animationStatus == NDAnimationStatusUnknown) {
[weakSelf.giftArray removeObject:gift];
[animationView startAnimationWithGift:gift finishedBlock:^(NDGiftModel *gift) {
// 执行完动画递归
[weakSelf animateNextGift];
}];
return;
}
}
}