iOS彈幕的原理分析與實現(xiàn)

寫在前邊

為便于大家學(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. 初始化三個彈幕1、2、3準備進入屏幕,DataSource為彈幕資源的數(shù)據(jù)來源地。


    1.初始化彈幕
  2. 當彈幕陸陸續(xù)續(xù)進入屏幕,飛行速度與彈幕長度相關(guān),每當其中一個彈道的彈幕完全進入屏幕后,則從數(shù)據(jù)池中取出一個彈幕在相應(yīng)彈道進入屏幕,如彈幕4。


    2. 彈幕進入屏幕
  3. 如果某條彈幕已經(jīng)完全飛出屏幕,則將此彈幕從屏幕中刪除,如彈幕1和彈幕3。


    3. 彈幕飛出屏幕
  4. 當彈幕全部飛出屏幕,回到步驟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];
}

最終展示效果如下:


效果展示

查看完整代碼,下載地址。
如何響應(yīng)彈幕的點擊事件請看 《續(xù)篇》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 寫在前面 之前寫了篇《彈幕的原理分析與實現(xiàn)》 的文章,最近有些閱讀的朋友提出了些疑問,大家比較關(guān)注的一個問題就是如...
    43f8d00feb3b閱讀 2,518評論 7 16
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,765評論 25 709
  • 隨著90后的不斷崛起,彈幕越來越受到年輕人的喜愛。所謂彈幕,我的理解就是評論的另一種表現(xiàn)形式,更能吸引用戶眼球,增...
    tianyulong丶閱讀 713評論 0 1
  • 夜深人靜 秒針滴嗒滴嗒轉(zhuǎn) 窗外飄著2017第一次初雪 漫天飛舞的雪花輕盈歡唱 聆聽CD輕輕傳來柔美的旋律 依舊充滿...
    龍伊生閱讀 312評論 1 4
  • docker images往往不知不覺就占滿了硬盤空間,為了清理冗余的image,可采用以下方法: 1.進入roo...
    cfygaoyang閱讀 5,046評論 2 2

友情鏈接更多精彩內(nèi)容