IOS 自定义弹幕实现

写在开篇

最近做了个视频直播项目,当用到弹幕时,找了很多网上弹幕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,如果遇到请在留言中指出,方便我好及时修改。或者你有什么更好的意见或建议,也可以提出来,我们相互交流、相互学习。又或者你将这个功能集成在项目中时,因为项目原因需要增加其他的功能,你也可以提出,我也会依据个人能力添加新的功能,或是给你一些建议。总之你可以在留言区发表你的想法,最后希望这篇文章能真正的帮助到大家。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容

  • 背景 一年多以前我在知乎上答了有关LeetCode的问题, 分享了一些自己做题目的经验。 张土汪:刷leetcod...
    土汪阅读 12,723评论 0 33
  • 1.badgeVaule气泡提示 2.git终端命令方法> pwd查看全部 >cd>ls >之后桌面找到文件夹内容...
    i得深刻方得S阅读 4,628评论 1 9
  • 前言 最近忙完项目比较闲,想写一篇博客来分享一些自学iOS的心得体会,希望对迷茫的你有所帮助。博主非科班出身,一些...
    GitHubPorter阅读 1,416评论 9 5
  • 概述在iOS开发中UITableView可以说是使用最广泛的控件,我们平时使用的软件中到处都可以看到它的影子,类似...
    liudhkk阅读 8,985评论 3 38
  • 无意中选中了『海上钢琴师』这部电影,男主从小就被家人抛弃,后来被一个烧锅炉的工人捡到,取了个挺长的名字,船上的工人...
    小镇姑娘W阅读 281评论 0 0