【全细节分析】- iOS端直播间礼物模块—2019

这个是从已上线半年的直播项目中抽出来的礼物模块,通过压测无任何问题。

准备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;
}

从上面的礼物视图状态:

  1. 当动画未开始和即将结束我们直接返回 NO,
  2. 动画即将开始了,这个时候我们又接收到同样的礼物我们只需要更新数量就可以了
  3. 动画处于连击状态中,同样的只需要更新礼物数量
  4. 动画处于悬浮在页面上,动画也停止了,这个时候我们需要更新数量,并且从新启动连击动画。

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;
        }
    }
}

最后就是demo地址啦~~~~ github地址

你可能感兴趣的:(【全细节分析】- iOS端直播间礼物模块—2019)