本篇內容通過步驟詳細給大家講解了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