寫在前邊
為便于大家學(xué)習(xí),在視頻網(wǎng)站上錄了相應(yīng)的視頻
想看視頻的朋友點這里喲~~
何為彈幕?~~
隨著90后的不斷崛起,彈幕越來越受到年輕人的喜愛。所謂彈幕,我的理解就是評論的另一種表現(xiàn)形式,更能吸引用戶眼球,增強用戶體驗,增加用戶參與感和使用粘度?,F(xiàn)在國內(nèi)比較火的彈幕類視頻網(wǎng)站A站和B站,深受年輕人群的追捧。下邊就是B站的一個展示效果。

彈幕demo
另外一些新聞資訊類的app也開始實現(xiàn)彈幕功能,為博得用戶喜愛,比如唔哩、微在、橘子娛樂等等。下面我們就來一起分析一下,彈幕在iOS端是如何實現(xiàn)的呢?
原理分析與實現(xiàn)~~
首先我們來分析一下彈幕的特點。
- 一般情況下彈幕都是從屏幕右側(cè)進入并從屏幕左側(cè)飛出。
- 彈幕進入屏幕后按照一定軌跡來移動。
- 彈幕移動速度根據(jù)內(nèi)容長度決定,內(nèi)容越長,移動速度越快。
- 一個彈幕完全進入屏幕后,后邊會繼續(xù)飛入一個新的彈幕。
- 彈幕是循環(huán)滾動播放的。
基于以上特點,我們設(shè)計出來的彈幕形式大致如下圖所示,默認只有三個彈道(彈幕飛行軌跡)來展示彈幕飛行效果。
-
初始化三個彈幕1、2、3準備進入屏幕,DataSource為彈幕資源的數(shù)據(jù)來源地。
1.初始化彈幕 -
當彈幕陸陸續(xù)續(xù)進入屏幕,飛行速度與彈幕長度相關(guān),每當其中一個彈道的彈幕完全進入屏幕后,則從數(shù)據(jù)池中取出一個彈幕在相應(yīng)彈道進入屏幕,如彈幕4。
2. 彈幕進入屏幕 -
如果某條彈幕已經(jīng)完全飛出屏幕,則將此彈幕從屏幕中刪除,如彈幕1和彈幕3。
3. 彈幕飛出屏幕 - 當彈幕全部飛出屏幕,回到步驟1,重新滾動播放
技術(shù)實現(xiàn)~~
下面從技術(shù)層面來討論一下實現(xiàn)細節(jié),以下是部分核心代碼,完整代碼參見這里。
首先來看一下彈幕的生成過程,初始化三個彈幕軌跡,如果不足三個,創(chuàng)建2個或者1個軌跡,代碼(BulletManager.m)如下:
- (void)start {
if (self.tmpComments.count == 0) {
[self.tmpComments addObjectsFromArray:self.allComments];
}
self.bStarted = YES;
self.bStopAnimation = NO;
[self initBulletCommentView];
}
/**
* 初始化彈幕
*/
- (void)initBulletCommentView {
//初始化三條彈幕軌跡
NSMutableArray *arr = [NSMutableArray arrayWithArray:@[@(0), @(1), @(2)]];
for (int i = 3; i > 0; i--) {
NSString *comment = [self.tmpComments firstObject];
if (comment) {
[self.tmpComments removeObjectAtIndex:0];
//隨機生成彈道創(chuàng)建彈幕進行展示(彈幕的隨機飛入效果)
NSInteger index = arc4random()%arr.count;
Trajectory trajectory = [[arr objectAtIndex:index] intValue];
[arr removeObjectAtIndex:index];
[self createBulletComment:comment trajectory:trajectory];
} else {
break;
}
}
}
創(chuàng)建彈幕view,對彈幕view的各種位置狀態(tài)進行監(jiān)聽并做出相對應(yīng)的處理。
/**
* 創(chuàng)建彈幕
*
* @param comment 彈幕內(nèi)容
* @param trajectory 彈道位置
*/
- (void)createBulletComment:(NSString *)comment trajectory:(Trajectory)trajectory {
if (self.bStopAnimation) {
return;
}
//創(chuàng)建一個彈幕view
BulletView *view = [[BulletView alloc] initWithContent:comment];
//設(shè)置運行軌跡
view.trajectory = trajectory;
__weak BulletView *weakBulletView = view;
__weak BulletManager *myself = self;
/**
* 彈幕view的動畫過程中的回調(diào)狀態(tài)
* Start:創(chuàng)建彈幕在進入屏幕之前
* Enter:彈幕完全進入屏幕
* End:彈幕飛出屏幕后
*/
view.moveBlock = ^(CommentMoveStatus status) {
if (myself.bStopAnimation) {
return ;
}
switch (status) {
case Start:
//彈幕開始……將view加入彈幕管理queue
[self.bulletQueue addObject:weakBulletView];
break;
case Enter: {
//彈幕完全進入屏幕,判斷接下來是否還有內(nèi)容,如果有則在該彈道軌跡對列中創(chuàng)建彈幕……
NSString *comment = [myself nextComment];
if (comment) {
[myself createBulletComment:comment trajectory:trajectory];
} else {
//說明到了評論的結(jié)尾了
}
break;
}
case End: {
//彈幕飛出屏幕后從彈幕管理queue中刪除
if ([myself.bulletQueue containsObject:weakBulletView]) {
[myself.bulletQueue removeObject:weakBulletView];
}
if (myself.bulletQueue.count == 0) {
//說明屏幕上已經(jīng)沒有彈幕評論了,循環(huán)開始
[myself start];
}
break;
}
default:
break;
}
};
//彈幕生成后,傳到viewcontroller進行頁面展示
if (self.generateBulletBlock) {
self.generateBulletBlock(view);
}
}
- (NSString *)nextComment {
NSString *comment = [self.tmpComments firstObject];
if (comment) {
[self.tmpComments removeObjectAtIndex:0];
}
return comment;
}
彈幕view的動畫執(zhí)行,部分代碼(BulletView.m)如下:
- (void)startAnimation {
//根據(jù)定義的duration計算速度以及完全進入屏幕的時間
CGFloat wholeWidth = CGRectGetWidth(self.frame) + mWidth + 50;
CGFloat speed = wholeWidth/mDuration;
CGFloat dur = (CGRectGetWidth(self.frame) + 50)/speed;
__block CGRect frame = self.frame;
if (self.moveBlock) {
//彈幕開始進入屏幕
self.moveBlock(Start);
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(dur * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//避免重復(fù),通過變量判斷是否已經(jīng)釋放了資源,釋放后,不在進行操作。
//在stopAnimation中 self.bDealloc = YES;
if (self.bDealloc) {
return;
}
//dur時間后彈幕完全進入屏幕
if (self.moveBlock) {
self.moveBlock(Enter);
}
});
//彈幕完全離開屏幕
[UIView animateWithDuration:mDuration delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
frame.origin.x = -CGRectGetWidth(frame);
self.frame = frame;
} completion:^(BOOL finished) {
if (self.moveBlock) {
self.moveBlock(End);
}
[self removeFromSuperview];
}];
}
在viewcontroller中直接調(diào)用以下代碼:
self.bulletManager = [[BulletManager alloc] init];
__weak ViewController *myself = self;
self.bulletManager.generateBulletBlock = ^(BulletView *bulletView) {
[myself addBulletView:bulletView];
};
- (void)addBulletView:(BulletView *)bulletView {
bulletView.frame = CGRectMake(CGRectGetWidth(self.view.frame)+50, 200 + 34 * bulletView.trajectory, CGRectGetWidth(bulletView.bounds), CGRectGetHeight(bulletView.bounds));
[self.view addSubview:bulletView];
[bulletView startAnimation];
}
//點擊開始按鈕,彈幕開始飛入屏幕
- (void)clickStart:(UIButton *)btn {
[self.bulletManager start];
}
最終展示效果如下:

效果展示


