iOS弹幕的原理分析与实现

写在前边

为便于大家学习,在视频网站上录了相应的视频
想看视频的朋友点这里哟~~

何为弹幕?~~

随着90后的不断崛起,弹幕越来越受到年轻人的喜爱。所谓弹幕,我的理解就是评论的另一种表现形式,更能吸引用户眼球,增强用户体验,增加用户参与感和使用粘度。现在国内比较火的弹幕类视频网站A站B站,深受年轻人群的追捧。下边就是B站的一个展示效果。

弹幕demo

另外一些新闻资讯类的app也开始实现弹幕功能,为博得用户喜爱,比如唔哩、微在、橘子娱乐等等。下面我们就来一起分析一下,弹幕在iOS端是如何实现的呢?

原理分析与实现~~

首先我们来分析一下弹幕的特点。

  • 一般情况下弹幕都是从屏幕右侧进入并从屏幕左侧飞出。
  • 弹幕进入屏幕后按照一定轨迹来移动。
  • 弹幕移动速度根据内容长度决定,内容越长,移动速度越快。
  • 一个弹幕完全进入屏幕后,后边会继续飞入一个新的弹幕。
  • 弹幕是循环滚动播放的。

基于以上特点,我们设计出来的弹幕形式大致如下图所示,默认只有三个弹道(弹幕飞行轨迹)来展示弹幕飞行效果。

  1. 初始化三个弹幕1、2、3准备进入屏幕,DataSource为弹幕资源的数据来源地。


    1.初始化弹幕
  2. 当弹幕陆陆续续进入屏幕,飞行速度与弹幕长度相关,每当其中一个弹道的弹幕完全进入屏幕后,则从数据池中取出一个弹幕在相应弹道进入屏幕,如弹幕4。


    2. 弹幕进入屏幕
  3. 如果某条弹幕已经完全飞出屏幕,则将此弹幕从屏幕中删除,如弹幕1和弹幕3。


    3. 弹幕飞出屏幕
  4. 当弹幕全部飞出屏幕,回到步骤1,重新滚动播放

技术实现~~

下面从技术层面来讨论一下实现细节,以下是部分核心代码,完整代码参见这里
首先来看一下弹幕的生成过程,初始化三个弹幕轨迹,如果不足三个,创建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];
            //随机生成弹道创建弹幕进行展示(弹幕的随机飞入效果)
            NSInteger index = arc4random()%arr.count;
            Trajectory trajectory = [[arr objectAtIndex:index] intValue];
            [arr removeObjectAtIndex:index];
            [self createBulletComment:comment trajectory:trajectory];
        } else {
            break;
        }
    }
}

创建弹幕view,对弹幕view的各种位置状态进行监听并做出相对应的处理。

 /**
  *  创建弹幕
  *
  *  @param comment    弹幕内容
  *  @param trajectory 弹道位置
  */
  - (void)createBulletComment:(NSString *)comment trajectory:(Trajectory)trajectory {
       if (self.bStopAnimation) {
           return;
       }
       //创建一个弹幕view
       BulletView *view = [[BulletView alloc] initWithContent:comment];
       //设置运行轨迹
       view.trajectory = trajectory;
       __weak BulletView *weakBulletView = view;
       __weak BulletManager *myself = self;
       /**
         *  弹幕view的动画过程中的回调状态
         *  Start:创建弹幕在进入屏幕之前
         *  Enter:弹幕完全进入屏幕
         *  End:弹幕飞出屏幕后  
         */             
       view.moveBlock = ^(CommentMoveStatus status) {
           if (myself.bStopAnimation) {
               return ;
           }
           switch (status) {
               case Start:
                   //弹幕开始……将view加入弹幕管理queue
                   [self.bulletQueue addObject:weakBulletView];
                   break;
               case Enter: {
                   //弹幕完全进入屏幕,判断接下来是否还有内容,如果有则在该弹道轨迹对列中创建弹幕……
                   NSString *comment = [myself nextComment];
                   if (comment) {
                       [myself createBulletComment:comment trajectory:trajectory];
                   } else {
                       //说明到了评论的结尾了
                   }
                   break;
               }
               case End: {
                   //弹幕飞出屏幕后从弹幕管理queue中删除
                   if ([myself.bulletQueue containsObject:weakBulletView]) {
                       [myself.bulletQueue removeObject:weakBulletView];
                   }
                   if (myself.bulletQueue.count == 0) {
                       //说明屏幕上已经没有弹幕评论了,循环开始
                       [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的动画执行,部分代码(BulletView.m)如下:

- (void)startAnimation {
    //根据定义的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(), ^{
       //避免重复,通过变量判断是否已经释放了资源,释放后,不在进行操作。
       //在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中直接调用以下代码:

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];
}

最终展示效果如下:


效果展示

查看完整代码,下载地址
如何响应弹幕的点击事件请看 《续篇》

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

推荐阅读更多精彩内容

  • 写在前面 之前写了篇《弹幕的原理分析与实现》 的文章,最近有些阅读的朋友提出了些疑问,大家比较关注的一个问题就是如...
    43f8d00feb3b阅读 2,366评论 7 16
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,263评论 25 707
  • 随着90后的不断崛起,弹幕越来越受到年轻人的喜爱。所谓弹幕,我的理解就是评论的另一种表现形式,更能吸引用户眼球,增...
    tianyulong丶阅读 644评论 0 1
  • 夜深人静 秒针滴嗒滴嗒转 窗外飘着2017第一次初雪 漫天飞舞的雪花轻盈欢唱 聆听CD轻轻传来柔美的旋律 依旧充满...
    龙伊生阅读 259评论 1 4
  • docker images往往不知不觉就占满了硬盘空间,为了清理冗余的image,可采用以下方法: 1.进入roo...
    cfygaoyang阅读 4,968评论 2 2