IOS 自定义弹幕实现

作者: Rasping | 来源:发表于2016-09-26 16:40 被阅读3110次

    写在开篇

    最近做了个视频直播项目,当用到弹幕时,找了很多网上弹幕demo。当时因为项目进度的原因,就随便选了一个漂亮的集成了,也没有去研究其中具体是如何实现的。如今项目完成,就利用空余的时间来研究了下。

    在我寻找一个合适的demo集成的时候,就发现网上提供的demo都是将一个lable作为一条弹幕,然后控制lable做动画,因为项目UI会对弹幕做明确的样式规定,所以单纯的一个lable根本就满足不了项目的需求。为什么不能将一个自定义的cell作为一条弹幕,控制cell做动画,作为使用者,则只用关心cell的样式和在cell上展示的数据呢?于是,我就开始了这篇文章。

    实现流程

    在实现一个功能之前我们需要知道这个功能是做什么的,然后根据这个功能构建一个大概的思路流程,最后将思路流程细化、修改、直至功能实现。

    弹幕的功能我就不必多说,而我实现这个思路流程与具体实现如下

    思路流程:

    1.收到弹幕消息数组,立即缓存

    2.定时从缓存中取出n条消息模型用来展示

    3.遍历取出的消息,为消息找一个合适的cell用来展示

    4.遍历每一条轨道(弹幕运动的路径),为弹幕找一条合适的轨道

    5.开始cell的动画,并清除缓存

    具体实现:

    1、消息缓存

    当收到弹幕消息时就调用BarrageView对象的insertBarrages:immediatelyShow:方法将消息添加到缓存中,具体实现如下:

    [BarrageView checkElementOfBarrages:barrages]; 

       self.count = barrages.count;

       if (barrages.count) {

          [self assortDataArray:barrages];

       }

       if (flag == YES && isStart == NO) {

       isStart = YES;

       [self startAnimation];

    }  

    在添加消息模型到缓存之前需要先对消息模型进行检测checkElementOfBarrages:因为在计算弹幕动画时间时必须要知道弹幕的宽度,所以消息模型必须要遵守BarrageModelAble协议,即告诉BarrageVIew弹幕的宽度

    2.取出模型

    从缓存中取模型subArrayWithNumber:方法具体实现如下:

    NSMutableArray *array = [NSMutableArray array];

    if (self.highPrioritys.count) {

       [array addObjectsFromArray:self.highPrioritys];

    }

    if (self.mediumPrioritys.count) {

       [array addObjectsFromArray:self.mediumPrioritys];

    }

    if (self.lowPrioritys.count) {

       [array addObjectsFromArray:self.lowPrioritys];

    }

    if (array.count >= number) {

       NSArray *subArray = [array subarrayWithRange:NSMakeRange(0, number)];

       [self removeModels:subArray];

       return subArray;

    }else {

       [self removeModels:array];

       return array;

    }   

    因为BarrageModelAble协议中要求对消息模型进行优先级(PriorityLevel)区分,我将三个不同优先级的数组按优先级顺序拼接成一个数组,然后从大数组中取出n个元素,并将这n个元素从缓存中删除

    3.谁来展示

    BarrageViewCell *currentCell = [self.dataSouce barrageView:self cellForRowAtIndex:[self.dataArray indexOfObject:obj]];

    寻找一个合适的cell来展示,根据标识符从cell的缓存池中取,如果取不到就创建一个cell。

    4.在哪展示

    遍历所有的轨道,有的轨道上没有弹幕--空闲轨道,有的轨道上有弹幕--非空闲轨道。所以需要对可用的轨道进行一个分类,具体实现如下:

    for (int row = 0; row < numbers; row++) {

       if (showCells.count == 0) {

       [freeOrbits addObject:[NSNumber numberWithInt:row]];

       }else {

       BOOL flag = NO;//标记row轨道上是否有cell正在执行动画

       for (int index = (int)showCells.count - 1; index >= 0; index--) {

       BarrageViewCell *cell = showCells[index];

       if (cell.row == row) {//找到row轨道上正在滚动的最后一条弹幕

          flag = YES;

          if ([self examineColide:cell] == NO) {

             [orbits addObject:[NSNumber numberWithInt:row]];

                }

          break;

          }

       }

       if (flag == NO) {

          [freeOrbits addObject:[NSNumber numberWithInt:row]];

          }

       }

    }   

    空闲轨道一定可用,但非空闲轨道还需要还需要进行碰撞检测examineColide:才能确定其是否可用,碰撞检测具体实现如下:

    - (BOOL)examineColide:(BarrageViewCell *)cell{

       NSTimeInterval t1 = self.duration - self.cellWidth / self.speed;

       NSDate *nowDate = [NSDate date];

       if (_stopDate) {

          nowDate = _stopDate;

       }

       NSDate *date1 = [nowDate dateByAddingTimeInterval:t1];

       NSDate *date2 = [cell.startTime dateByAddingTimeInterval:cell.duration];

       if ([date1 compare:date2] == NSOrderedDescending) {

          return NO;

       }else {

          return YES;

       }

    }  

    碰撞检测主要是根据当前轨道上最后一个正在滚动的弹幕的结束时间与当前需要开始滚动的弹幕从现在开始滚动的结束时间进行一个对比。

    选出可用轨道之后,就从可用轨道中随机出一个轨道,用于弹幕展示,当然先从空闲轨道中随。具体实现如下:

    if (orbits.count == 0 && freeOrbits.count == 0) {

       _row = -1;

    }else {

       if (freeOrbits.count > 0) {

          int index = arc4random_uniform((u_int32_t)freeOrbits.count);

          _row      = freeOrbits[index].integerValue; 

       }else {

          int index = arc4random_uniform((u_int32_t)orbits.count);

          _row      = orbits[index].integerValue;

       }

    }   

    5.如何展示

    至于弹幕的动画就非常简单了,即控制cell让其从屏幕右边滚动到屏幕左边,具体实现如下:

    CABasicAnimation *move  = [CABasicAnimation animation];

    move.keyPath            = @"position";

    CGRect frame            = self.frame;

    CGPoint fromPoint        = CGPointMake(frame.origin.x, frame.origin.y);

    CGPoint toPoint          = CGPointMake(- CGRectGetWidth(frame), frame.origin.y);

    move.fromValue          = [NSValue valueWithCGPoint:fromPoint];

    move.toValue            = [NSValue valueWithCGPoint:toPoint];

    move.duration            = duration;

    move.delegate            = self;

    move.removedOnCompletion = YES;

    [self.layer addAnimation:move forKey:nil];  

    动画是在异步线程中执行,动画添加到图层中之后就应该重复到第二步,从缓存中取新的消息模型用来展示,直到没有缓存数据为止。

    至此,就大功告成了,下面是实现的效果图

    效果图

    功能如何使用可以参照demo,最后附上Demo地址。

    写在结尾

    因为个人能力有限,demo中难免有些BUG,如果遇到请在留言中指出,方便我好及时修改。或者你有什么更好的意见或建议,也可以提出来,我们相互交流、相互学习。又或者你将这个功能集成在项目中时,因为项目原因需要增加其他的功能,你也可以提出,我也会依据个人能力添加新的功能,或是给你一些建议。总之你可以在留言区发表你的想法,最后希望这篇文章能真正的帮助到大家。

    相关文章

      网友评论

      • lnfg:非常好(✪▽✪)😂😂😂😂😂😂😂😬😬😬😬😬😬
      • 贫僧只用海飞丝:BarrageViewCell *currentCell = [self.dataSouce barrageView:self cellForModel:obj];怎么崩溃在这里,提示 [GGBulletScreenTableViewCell barrageView:cellForModel:]: unrecognized selector sent to instance 0x152238a00,但是对象都有值
      • 确认过眼神啊:请问作者是怎么获取弹幕的?
      • firecolen:你好,请问一下,当前vc在运行弹幕,push到下一个页面,然后再返回,当前展示的弹幕就会突然消失了。
      • WLAnswer:我调用停止动画后再调用开始动画,崩溃了,在找轨道的时候showcellS数组为空了
      • G_M:您好,我想问一下,为什么我在用您这个弹幕的时候,弹幕会很快速的一划而过,是我没有继承什么代理吗?
      • smkoc:这篇文章看到过
      • 一座城漫天飞着蒲公英:你这个检测轨道还是有问题。(当你的弹幕的内容越长,而你设置self.speedBaseVlaue固定,导致计算速度就越快,这个时候当弹幕刚好完全进入视图的所花费的时间的就越短。因此就导致越长的弹幕速度会越快,有时候你会看到后面越长的弹幕超过了前面越短的弹幕。就如果物理原理 S = vt)
        - (NSTimeInterval)calculateAnimationDurationWithBarrageWidth:(CGFloat)width spacing:(CGFloat)spacing cellWidth:(CGFloat)cellWidth speedBaseVlaue:(CGFloat)vlaue
        {
        _cellWidth = cellWidth;
        CGFloat wholeWidth = width + cellWidth + spacing;

        // 当长度越长,时间固定,导致速度越快,
        _speed = wholeWidth / vlaue;

        // 当速度变大,路程不变,所花费的时间越短
        _duration = (width + spacing) / _speed;

        return _duration;
        }

        还有一个问题
        /*******加载100条弹幕的模型数据(这里有错对于优先级没有进行赋值)*******/
        - (NSArray *)dataArray
        {
        if (!_dataArray) {

        NSString *path = [[NSBundle mainBundle] pathForResource:@"BarrageFile" ofType:@"plist"];


        NSMutableArray *array = [BarrageModel mj_objectArrayWithFile:path];


        _dataArray = [array subarrayWithRange:NSMakeRange(0, 200)];



        }

        return _dataArray;

        }

      • 博尔茨杰:直播弹幕的横竖屏播放有些问题,方便沟通吗?
      • 沉默的鱼sunny:文中的demo是最新的,想下载最新的学习学习!
      • Callmewenxi:服务器的通知是一条条消息过来的,这时候要怎么做才比较好?现在使用的时候发现有丢数据还有重复的问题
        Callmewenxi: @Rasping 我已经解决了。。。在添加数据的时候出问题了
        Callmewenxi:@Rasping 好的,谢谢,我查了好久,找不到问题所在.你在使用的时候,服务器过来的消息应该也是一条一条过来的吧?你是怎么处理的?
        Rasping:@iOS小小白 前两天比较忙,你说的问题我现在正在解决。
      • return以诺:能不能让速度不按照文字来计算,速度都一样
        Rasping:@iOS小小白 BarrageViewcell.m类中有一个speed属性,你搜索这个属性,把它改成你需要的就行了
        Callmewenxi:@Rasping 具体怎么修改呢,我改了一下,发现改出问题了,我在算动画时间的地方修改了
        Rasping:@return以诺 可以的,只是需要做些修改
      • Carsion:如果发弹幕的文字很长的话,也会出现速度很快的情况,
        Rasping:@Carsion 弹幕的速度是根据文字长短来确定的。
      • 半岛小铁盒:我用纯代码把 BarrageView 加到view上面 弹幕速度变快了 是什么原因?
        Rasping:@半岛小铁盒 你看下我这份代码https://pan.baidu.com/s/1c2NxFAK,对碰撞进行了优化。
        半岛小铁盒:@Rasping 找到原因了 初始化的问题 按照你的数据来 好像还是会有碰撞的 后面出现的弹幕速度过快 覆盖了前面的
        Rasping:@半岛小铁盒 这应该是速度在初始化的时候没有设置好,不过这个还需要确认下。你可以先通过BarrageView对象的speedBaseVlaue属性调节速度,等找出原因了我再给你回复。
      • itzhangbao:感谢作者的热情,我试了一下,随机从数组里抽取model添加到barrageview上还是会崩溃到
        //1.谁来展示
        BarrageViewCell *currentCell = [self.dataSouce barrageView:self cellForRowAtIndex:[self.dataArray indexOfObject:obj]];

        我的解决办法是

        //1.谁来展示
        BarrageViewCell *currentCell = [self.dataSouce barrageView:self model:obj];

        直接将模型传递出去!
      • 076674d54c1c:文字展示不全
        076674d54c1c:@Rasping 从git下载的有这种问题,网盘的没有,以上
        076674d54c1c:@Rasping 前几屏展示完全的,后来的就不行了,有“23......” 、“生 ”的情况出现
        Rasping:@星_安啦 那不是文字展示不全,而是数据就是那样的。你可以看下数据源文件BarrageFile.plist 就知道了
      • itzhangbao:这是一下插入这么多,我要是一条一条的插入会报错。
        Rasping:@秋天的阳光 这个问题已解决!但是不要利用循环往里面插入数据,毕竟计算是需要一定时间的,而且真实应用场景也不会如此!
        Rasping:@秋天的阳光 谢谢你的反馈!我会立刻修复!
      • Carsion:内存会一直慢慢增加??
        Carsion:@Rasping 还发现一个问题 、当在二级页面展示弹幕时 。我pop返回一级页面。在进入二级页面的时候弹幕不展示了
        Carsion:@Rasping 好的 期待你的更新
        Rasping:@Carsion 测试的时候使用的是很少的数据,就没注意到这个问题!现在正在修改!
      • itzhangbao:很好

      本文标题:IOS 自定义弹幕实现

      本文链接:https://www.haomeiwen.com/subject/qhcnettx.html