代码详解iOS视频直播弹幕功能

2020-01-21 03:08:33王旭


/**
 * 记录当前最后一个弹幕View,通过这个View来计算是显示在哪个弹幕轨道上
 */
@property(nonatomic,retain) UIView *lastAnimateView;
/**
 * 发送弹幕
 *
 * @param msgModel 弹幕数据Model
 */
-(void)barrageSendMsg:(BarrageModel *)msgModel;
@end
在.m文件中
#import "BarrageView.h"
#import "BarrageModel.h"
//屏幕的尺寸
#define SCREEN_FRAME  [[UIScreen mainScreen] bounds]
//屏幕的高度
#define SCREEN_HEIGHT CGRectGetHeight(SCREEN_FRAME)
//屏幕的宽度
#define SCREEN_WIDTH CGRectGetWidth(SCREEN_FRAME)


@interface BarrageView()
{
  CGFloat _minSpaceTime; /** 最小间距时间 */
}
/** 数据源 */
@property (nonatomic,retain)NSMutableArray *dataArr;


/** 弹幕UI的重用池 */
@property (nonatomic,retain)NSMutableArray *resuingArr;
@end
@implementation BarrageView
- (instancetype)initWithFrame:(CGRect)frame
{
  self = [super initWithFrame:frame];
  if (self) {
    [self setInterface];
  }
  return self;
}
-(void)setInterface
{
  //初始化弹幕数据源,以及重用池
  self.dataArr = [NSMutableArray array];
  self.resuingArr = [NSMutableArray array];
  //创建第一个弹幕加入重用池作为备用
  UIView *view = [self createUI];
  [self.resuingArr addObject:view];
  //设置弹幕数据的初始轮询时间
  _minSpaceTime = 1;
  //检查是否可以取弹幕数据进行动画
  [self checkStartAnimatiom];
}
-(void)checkStartAnimatiom
{
  //当有数据信息的时候
  if (self.dataArr.count>0) {
    if (self.resuingArr.count>0) { //当重用池里面有备用的弹幕UI时
      
      //在重用池中,取出第一个弹幕UI
      UIView *view = [self.resuingArr firstObject];
      [self.resuingArr removeObject:view];
      //取出的这个弹幕UI开始动画
      [self startAnimationWithView:view];
      
    }else{ //当重用池没有备用的弹幕UI时
      
      //重新创建一个弹幕UI
      UIView *view = [self createUI];
      //拿着这个弹幕UI开始动画
      [self startAnimationWithView:view];
    }
  }
  //延迟执行,在主线程中不能调用sleep()进行延迟执行
  //调用自身方法,构成一个无限循环,不停的轮询检查是否有弹幕数据
  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_minSpaceTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    [self checkStartAnimatiom];
  });
}
-(void)startAnimationWithView:(UIView *)view
{
  //取出第一条数据
  BarrageModel *barrageModel = [self.dataArr firstObject];
  //计算昵称的长度
  CGSize nameSize = [barrageModel.userName boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, 14) options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading attributes:@{                                                                         } context:nil].size;
  //计算消息的长度
  CGSize msgSize = [barrageModel.userMsg boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, 14) options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading attributes:@{                                                               NSFontAttributeName:[UIFont systemFontOfSize:14]
} context:nil].size; 
  UIImageView *headImageView; //头像
  UILabel *userNameLabel;   //昵称
  UILabel *userMsgLabel;   //消息内容
  //进行赋值,宽度适应
  for (UIView *subView in view.subviews) {
    if (subView.tag == 1000) {
      headImageView = (UIImageView *)subView;
      headImageView.image = [UIImage imageNamed:@""];
      
    }else if (subView.tag == 1001){
      userNameLabel = (UILabel *)subView;
      userNameLabel.text = barrageModel.userName;
      //重新设置名称Label宽度
      CGRect nameRect = userNameLabel.frame;
      nameRect.size.width = nameSize.width;
      userNameLabel.frame = nameRect;
    }else{
      userMsgLabel = (UILabel *)subView;
      userMsgLabel.text = barrageModel.userMsg;
      //重新设置消息内容Label宽度
      CGRect msgRect = userMsgLabel.frame;
      msgRect.size.width = msgSize.width;
      userMsgLabel.frame = msgRect;
    }
  }
  //重新设置弹幕的总体宽度 = 头像宽度 + 头像左右两侧距离 + (如果名字宽度大于消息内容宽度,以名字宽度为基准,如果名字宽度小于消息内容宽度,以消息内容宽度为基准)
  view.frame = CGRectMake(SCREEN_WIDTH, 0, CGRectGetWidth(headImageView.frame) + 4 + (CGRectGetWidth(userNameLabel.frame)>CGRectGetWidth(userMsgLabel.frame)?CGRectGetWidth(userNameLabel.frame):CGRectGetWidth(userMsgLabel.frame)), CGRectGetHeight(self.frame));
  //不管弹幕长短,速度要求一致。 V(速度) 为固定值 = 100(可根据实际自己调整)
  // S = 屏幕宽度+弹幕的宽度 V = 100(可根据实际自己调整)
  // V(速度) = S(路程)/t(时间) -------> t(时间) = S(路程)/V(速度);
  CGFloat duration = (view.frame.size.width+SCREEN_WIDTH)/100;
  //最小间距运行时间为:弹幕从屏幕外完全移入屏幕内的时间 + 间距的时间
  _minSpaceTime = (view.frame.size.width + 30)/100;
  //最后做动画的view
  _lastAnimateView = view;  
  //弹幕UI开始动画
  [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
    //运行至左侧屏幕外
    CGRect frame = view.frame;
    view.frame = CGRectMake(-frame.size.width, 0, frame.size.width, frame.size.height);
  } completion:^(BOOL finished) {
    //动画结束重新回到右侧初始位置
    view.frame = CGRectMake(SCREEN_WIDTH, 0, 0, CGRectGetHeight(self.frame));
    //重新加入重用池
    [self.resuingArr addObject:view];
  }];  
  //将这个弹幕数据移除
  [self.dataArr removeObject:barrageModel];
}
#pragma mark public method
-(void)barrageSendMsg:(BarrageModel *)msgModel{
  //添加弹幕数据
  [self.dataArr addObject:msgModel];
}
#pragma mark 创建控件
-(UIView *)createUI
{
  UIView *view = [[UIView alloc] initWithFrame:CGRectMake(SCREEN_WIDTH, 0, 0, CGRectGetHeight(self.frame))];
  view.backgroundColor = [UIColor colorWithWhite:0 alpha:0.3];
  UIImageView *headImageView = [[UIImageView alloc] initWithFrame:CGRectMake(2, 2, CGRectGetHeight(self.frame)-4, CGRectGetHeight(self.frame)-4)];
  headImageView.layer.cornerRadius = headImageView.frame.size.width/2;
  headImageView.layer.masksToBounds = YES;
  headImageView.tag = 1000;
  headImageView.backgroundColor = [UIColor redColor];
  [view addSubview:headImageView];
  UILabel *userNameLabel = [[UILabel alloc] initWithFrame:CGRectMake(CGRectGetMaxX(headImageView.frame) + 2, 0, 0,14)];
  userNameLabel.font = [UIFont systemFontOfSize:14];
  userNameLabel.tag = 1001;
  [view addSubview:userNameLabel];
  UILabel *userMsgLabel = [[UILabel alloc] initWithFrame:CGRectMake(CGRectGetMaxX(headImageView.frame)+2, CGRectGetMaxY(userNameLabel.frame), 0, 14)];
  userMsgLabel.font = [UIFont systemFontOfSize:14];
  userMsgLabel.tag = 1002;
  [view addSubview:userMsgLabel];
  [self addSubview:view];
  return view;
}