本篇内容通过步骤详细给大家讲解了ios视频直播弹幕的原理以及实现代码分析,以下就是全部内容:
1.弹幕的实现性分析
首先,从视觉上明确当前弹幕所具有的功能
从屏幕右侧滑入左侧,直至完全消失
不管是长的弹幕,还是短的弹幕,速度一致(可能有的需求是依据弹幕长度,调整速度)
有弹幕轨道,不是随机产生的弹幕
弹幕不会进行重叠
接下来从功能角度思考需要做什么
重用机制,类似tableview有一个重用池,每个弹幕就是一个cell,当有弹幕发送的时候,如果当前的重用池没有控件,则创建一个新的控件,如果重用池里面有控件,则拿出这个控件,开始做动画,在动画结束后重新将该控件重归重用池。
速度要求一致的话,需要考虑几点,首先如下图所示,红色代表弹幕起始位置,蓝色代表弹幕终止位置,长度代表它们的实际长度。当我们设定动画的时候,采用[uiview animationwithduration.....]这个动画,设定duration为3s的话那么弹幕1的速度为(屏幕宽度+弹幕1宽度)/3,弹幕2的速度为(屏幕宽度+弹幕2宽度)/3,因为弹幕2长度大于弹幕1的长度,所以弹幕2的速度大于弹幕1的速度。(对于依据弹幕长度调整速度的需求来说,这里相对简单一些,不需要专门去计算速度,唯一麻烦的是需要考虑速度不一致带来的重叠问题)
2.开始准备
精通数学公式v=s/t (v代表速度,s代表路程,t代表时间)(*^__^*)
3.正式开始
创建一个view,命名为barrageview,以及存储弹幕数据的对象barragemodel
以下为barragemodel.h的内容,存储弹幕的头像,昵称,和消息内容
1
2
3
4
5
6
7
8
|
@interface barragemodel : nsobject
/** 用户昵称 */
@property(nonatomic,copy)nsstring *username;
/** 消息内容 */
@property(nonatomic,copy)nsstring *usermsg;
/** 用户头像 */
@property(nonatomic,copy)nsstring *userheadimageurl;
@end
|
接下来对barrageview内容进行编辑,注释已经尽可能的详细,因此不多做介绍
在.h文件中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#import <uikit/uikit.h>
@ class barragemodel;
@interface barrageview : uiview
/**
* 记录当前最后一个弹幕view,通过这个view来计算是显示在哪个弹幕轨道上
*/
@property(nonatomic,retain) uiview *lastanimateview;
/**
* 发送弹幕
*
* @param msgmodel 弹幕数据model
*/
-( void )barragesendmsg:(barragemodel *)msgmodel;
@end
|
在.m文件中
1
|
#import <uikit/uikit.h><br>@class barragemodel;<br>@interface barrageview : uiview
|
1
|
/**<br> * 记录当前最后一个弹幕view,通过这个view来计算是显示在哪个弹幕轨道上<br> */ <br>@property(nonatomic,retain) uiview *lastanimateview;<br> /**<br> * 发送弹幕<br> *<br> * @param msgmodel 弹幕数据model<br> */ <br>-( void )barragesendmsg:(barragemodel *)msgmodel;<br>@end<br>在.m文件中<br>#import "barrageview.h" <br>#import "barragemodel.h" <br> //屏幕的尺寸<br>#define screen_frame [[uiscreen mainscreen] bounds]<br>//屏幕的高度<br>#define screen_height cgrectgetheight(screen_frame)<br>//屏幕的宽度<br>#define screen_width cgrectgetwidth(screen_frame)
|
1
|
@interface barrageview()<br>{<br> cgfloat _minspacetime; /** 最小间距时间 */ <br>}<br> /** 数据源 */ <br>@property (nonatomic,retain)nsmutablearray *dataarr;
|
1
|
/** 弹幕ui的重用池 */ <br>@property (nonatomic,retain)nsmutablearray *resuingarr;<br>@end<br>@implementation barrageview<br>- (instancetype)initwithframe:(cgrect)frame<br>{<br> self = [super initwithframe:frame];<br> if (self) {<br> [self setinterface];<br> }<br> return self;<br>}<br>-( void )setinterface<br>{<br> //初始化弹幕数据源,以及重用池<br> self.dataarr = [nsmutablearray array];<br> self.resuingarr = [nsmutablearray array];<br> //创建第一个弹幕加入重用池作为备用<br> uiview *view = [self createui];<br> [self.resuingarr addobject:view];<br> //设置弹幕数据的初始轮询时间<br> _minspacetime = 1;<br> //检查是否可以取弹幕数据进行动画<br> [self checkstartanimatiom];<br>}<br>-(void)checkstartanimatiom<br>{<br> //当有数据信息的时候<br> if (self.dataarr.count>0) {<br> if (self.resuingarr.count>0) { //当重用池里面有备用的弹幕ui时<br> <br> //在重用池中,取出第一个弹幕ui<br> uiview *view = [self.resuingarr firstobject];<br> [self.resuingarr removeobject:view];<br> //取出的这个弹幕ui开始动画<br> [self startanimationwithview:view];<br> <br> }else{ //当重用池没有备用的弹幕ui时<br> <br> //重新创建一个弹幕ui<br> uiview *view = [self createui];<br> //拿着这个弹幕ui开始动画<br> [self startanimationwithview:view];<br> }<br> }<br> //延迟执行,在主线程中不能调用sleep()进行延迟执行<br> //调用自身方法,构成一个无限循环,不停的轮询检查是否有弹幕数据<br> dispatch_after(dispatch_time(dispatch_time_now, (int64_t)(_minspacetime * nsec_per_sec)), dispatch_get_main_queue(), ^{<br> [self checkstartanimatiom];<br> });<br>}<br>-(void)startanimationwithview:(uiview *)view<br>{<br> //取出第一条数据<br> barragemodel *barragemodel = [self.dataarr firstobject];<br> //计算昵称的长度<br> cgsize namesize = [barragemodel.username boundingrectwithsize:cgsizemake(cgfloat_max, 14) options:nsstringdrawinguseslinefragmentorigin|nsstringdrawingusesfontleading attributes:@{ } context:nil].size;<br> //计算消息的长度<br> cgsize msgsize = [barragemodel.usermsg boundingrectwithsize:cgsizemake(cgfloat_max, 14) options:nsstringdrawinguseslinefragmentorigin|nsstringdrawingusesfontleading attributes:@{ nsfontattributename:[uifont systemfontofsize:14]<br>} context:nil].size; <br> uiimageview *headimageview; //头像<br> uilabel *usernamelabel; //昵称<br> uilabel *usermsglabel; //消息内容<br> //进行赋值,宽度适应<br> for (uiview *subview in view.subviews) {<br> if (subview.tag == 1000) {<br> headimageview = (uiimageview *)subview;<br> headimageview.image = [uiimage imagenamed:@""];<br> <br> }else if (subview.tag == 1001){<br> usernamelabel = (uilabel *)subview;<br> usernamelabel.text = barragemodel.username;<br> //重新设置名称label宽度<br> cgrect namerect = usernamelabel.frame;<br> namerect.size.width = namesize.width;<br> usernamelabel.frame = namerect;<br> }else{<br> usermsglabel = (uilabel *)subview;<br> usermsglabel.text = barragemodel.usermsg;<br> //重新设置消息内容label宽度<br> cgrect msgrect = usermsglabel.frame;<br> msgrect.size.width = msgsize.width;<br> usermsglabel.frame = msgrect;<br> }<br> }<br> //重新设置弹幕的总体宽度 = 头像宽度 + 头像左右两侧距离 + (如果名字宽度大于消息内容宽度,以名字宽度为基准,如果名字宽度小于消息内容宽度,以消息内容宽度为基准)<br> 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));<br> //不管弹幕长短,速度要求一致。 v(速度) 为固定值 = 100(可根据实际自己调整)<br> // s = 屏幕宽度+弹幕的宽度 v = 100(可根据实际自己调整)<br> // v(速度) = s(路程)/t(时间) -------> t(时间) = s(路程)/v(速度);<br> cgfloat duration = (view.frame.size.width+screen_width)/100;<br> //最小间距运行时间为:弹幕从屏幕外完全移入屏幕内的时间 + 间距的时间<br> _minspacetime = (view.frame.size.width + 30)/100;<br> //最后做动画的view<br> _lastanimateview = view; <br> //弹幕ui开始动画<br> [uiview animatewithduration:duration delay:0 options:uiviewanimationoptioncurvelinear animations:^{<br> //运行至左侧屏幕外<br> cgrect frame = view.frame;<br> view.frame = cgrectmake(-frame.size.width, 0, frame.size.width, frame.size.height);<br> } completion:^(bool finished) {<br> //动画结束重新回到右侧初始位置<br> view.frame = cgrectmake(screen_width, 0, 0, cgrectgetheight(self.frame));<br> //重新加入重用池<br> [self.resuingarr addobject:view];<br> }]; <br> //将这个弹幕数据移除<br> [self.dataarr removeobject:barragemodel];<br>}<br>#pragma mark public method<br>-(void)barragesendmsg:(barragemodel *)msgmodel{<br> //添加弹幕数据<br> [self.dataarr addobject:msgmodel];<br>}<br>#pragma mark 创建控件<br>-(uiview *)createui<br>{<br> uiview *view = [[uiview alloc] initwithframe:cgrectmake(screen_width, 0, 0, cgrectgetheight(self.frame))];<br> view.backgroundcolor = [uicolor colorwithwhite:0 alpha:0.3];<br> uiimageview *headimageview = [[uiimageview alloc] initwithframe:cgrectmake(2, 2, cgrectgetheight(self.frame)-4, cgrectgetheight(self.frame)-4)];<br> headimageview.layer.cornerradius = headimageview.frame.size.width/2;<br> headimageview.layer.maskstobounds = yes;<br> headimageview.tag = 1000;<br> headimageview.backgroundcolor = [uicolor redcolor];<br> [view addsubview:headimageview];<br> uilabel *usernamelabel = [[uilabel alloc] initwithframe:cgrectmake(cgrectgetmaxx(headimageview.frame) + 2, 0, 0,14)];<br> usernamelabel.font = [uifont systemfontofsize:14];<br> usernamelabel.tag = 1001;<br> [view addsubview:usernamelabel];<br> uilabel *usermsglabel = [[uilabel alloc] initwithframe:cgrectmake(cgrectgetmaxx(headimageview.frame)+2, cgrectgetmaxy(usernamelabel.frame), 0, 14)];<br> usermsglabel.font = [uifont systemfontofsize:14];<br> usermsglabel.tag = 1002;<br> [view addsubview:usermsglabel];<br> [self addsubview:view];<br> return view;<br>}<br>
|
最后在vc里面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
#import "viewcontroller.h"
#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 viewcontroller ()
/** 第一个弹幕轨道 */
@property (nonatomic,retain)barrageview *barrageviewone;
/** 第二个弹幕轨道 */
@property (nonatomic,retain)barrageview *barrageviewtwo;
@end
@implementation viewcontroller
- ( void )viewdidload {
[super viewdidload];
//创建第一个弹幕轨道
_barrageviewone = [[barrageview alloc]initwithframe:cgrectmake(0,200, screen_width, 34)];
[self.view addsubview:_barrageviewone];
//创建第二个弹幕轨道
_barrageviewtwo = [[barrageview alloc]initwithframe:cgrectmake(0,300, screen_width, 34)];
[self.view addsubview:_barrageviewtwo];
}
-( void )touchesbegan:(nsset<uitouch *> *)touches withevent:(uievent *)event
{
nstimer *timer = [nstimer scheduledtimerwithtimeinterval:1 target:self selector:@selector(sendmessage) userinfo:nil repeats:yes];
[timer fire];
}
-( void )sendmessage
{
barragemodel *model = [[barragemodel alloc]init];
model.username = @[@ "张三" ,@ "李四" ,@ "王五" ,@ "赵六" ,@ "七七" ,@ "八八" ,@ "九九" ,@ "十十" ,@ "十一" ,@ "十二" ,@ "十三" ,@ "十四" ][arc4random()%12];
model.usermsg = @[@ "阿达个人" ,@ "都是vsqe12qwe" ,@ "胜多负少的凡人歌" ,@ "委屈翁二群二" ,@ "12312" ,@ "热帖柔荑花" ,@ "发彼此彼此" ,@ "ok泼墨" ,@ "人体有图图" ,@ "额外热无若无" ,@ "微软将围" ][arc4random()%11];
//计算当前做动画的弹幕ui的位置
cgfloat onepositon = _barrageviewone.lastanimateview.layer.presentationlayer.frame.size.width + _barrageviewone.lastanimateview.layer.presentationlayer.frame.origin.x;
//计算当前做动画的弹幕ui的位置
cgfloat twopositon = _barrageviewtwo.lastanimateview.layer.presentationlayer.frame.size.width + _barrageviewtwo.lastanimateview.layer.presentationlayer.frame.origin.x;
if ( onepositon < twopositon ) {
[_barrageviewone barragesendmsg:model];
} else {
[_barrageviewtwo barragesendmsg:model];
}
}
@end
|
4.测试结论
经一个小时的定时器测试,内存没有增加。
原文链接:http://www.cnblogs.com/ChengYing-Freedom/p/8025210.html