iOS 弹幕制作

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS 弹幕制作相关的知识,希望对你有一定的参考价值。

离职的最后一天,在公司学习下弹幕的制作.基于OC.

主要思路:

    1.首先建一个弹幕类BulletView,基于UIView,然后在该类上写个UIlabel,用于放置弹幕文字,然后前端放置一个UIImageView,放置用户头像.该类主要绘制UI和动画.

    2.其次建立一个弹幕的管理类BulletManager,主要管理弹幕数据源,随机分配弹幕轨迹,根据不同状态(start,enter,end)做不同处理,该类主要负责逻辑部分.

其中,在弹幕类BulletView中写一个回调,负责回调当前弹幕的状态(start,enter,end)给管理类BulletManager;在管理类BulletManage写一个回调,负责回调弹幕视图给ViewController.

弹幕类:

BulletView.h

 1 //
 2 //  BulletView.h
 3 //  danMu
 4 //
 5 //  Created by Shaoting Zhou on 2017/9/11.
 6 //  Copyright © 2017年 Shaoting Zhou. All rights reserved.
 7 //
 8 
 9 #import <UIKit/UIKit.h>
10 typedef NS_ENUM(NSInteger,MoveStatus){
11     Start,
12     Enter,
13     End,
14 };
15 @interface BulletView : UIView
16 @property (nonatomic,assign) int trajectory;  //弹幕弹道
17 @property (nonatomic,copy) void(^ moveStatusBlock)(MoveStatus status);  //弹幕状态回调 开始  运行中 结束
18 
19 -(instancetype)initWithCommentDic:(NSDictionary *)dic;   //初始化弹幕
20 
21 -(void)startAnimation;  //开始动画
22 -(void)stopAnimation; //结束动画
23 
24 @end
View Code

BulletView.m

  1 //
  2 //  BulletView.m
  3 //  danMu
  4 //
  5 //  Created by Shaoting Zhou on 2017/9/11.
  6 //  Copyright © 2017年 Shaoting Zhou. All rights reserved.
  7 //
  8 
  9 #import "BulletView.h"
 10 
 11 #define padding 10
 12 #define imgHeight 30
 13 @interface BulletView()
 14 @property (nonatomic,strong) UILabel * lbComment;
 15 @property (nonatomic,strong) UIImageView * imgView;
 16 
 17 @end
 18 
 19 @implementation BulletView
 20 
 21 //MARK: 初始化弹幕
 22 -(instancetype)initWithCommentDic:(NSDictionary *)dic{
 23     if(self = [super init]){
 24         self.layer.cornerRadius = 30/2;
 25         
 26         CGFloat colorR = arc4random()%255;
 27         CGFloat colorG = arc4random()%255;
 28         CGFloat colorB = arc4random()%255;
 29         self.backgroundColor = [UIColor colorWithRed:colorR/255 green:colorG/255 blue:colorB/255 alpha:1.0];
 30         
 31         //计算弹幕的实际宽度
 32         NSDictionary *attr = @{NSFontAttributeName:[UIFont systemFontOfSize:14]};
 33         NSString * comment = dic[@"danmu"];
 34         CGFloat width = [comment sizeWithAttributes:attr].width;
 35         self.bounds = CGRectMake(0, 0, width + 2 * padding + imgHeight , 30);
 36         self.lbComment.text = comment;
 37         self.lbComment.frame = CGRectMake(padding + imgHeight, 0, width, 30);
 38         
 39         //头像
 40         self.imgView.frame = CGRectMake(-padding, -padding, imgHeight + padding,  imgHeight + padding);
 41         self.imgView.layer.cornerRadius =  (imgHeight + padding)/2;
 42         NSURL * url = [NSURL URLWithString:dic[@"userPhoto"]];
 43         self.imgView.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];;
 44         
 45 //        NSLog(@"%@",comment);
 46     }
 47     return self;
 48 }
 49 
 50 -(UILabel *)lbComment{
 51     if(!_lbComment){
 52         self.lbComment = [[UILabel alloc]initWithFrame:CGRectZero];
 53         self.lbComment.font = [UIFont systemFontOfSize:14];
 54         self.lbComment.textColor = [UIColor whiteColor];
 55         self.lbComment.textAlignment =  NSTextAlignmentCenter;
 56         [self addSubview:self.lbComment];
 57 
 58     }
 59     return _lbComment;
 60 }
 61 
 62 -(UIImageView *)imgView{
 63     if(!_imgView){
 64         self.imgView = [UIImageView new];
 65         self.imgView.clipsToBounds = YES;
 66         self.imgView.contentMode = UIViewContentModeScaleAspectFill;
 67         [self addSubview:self.imgView];
 68     }
 69     return _imgView;
 70 }
 71 
 72 
 73 
 74 //MARK:开始动画
 75 -(void)startAnimation{
 76     CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
 77     CGFloat duration = 4.0f;
 78     CGFloat wholeWidth = screenWidth + CGRectGetWidth(self.bounds);
 79     
 80 //    弹幕开始
 81     if(self.moveStatusBlock){
 82         self.moveStatusBlock(Start);
 83     }
 84     
 85     CGFloat speed = wholeWidth/duration;   // v =  s/t
 86     CGFloat enterDuration = CGRectGetWidth(self.bounds)/speed;  //完全进入屏幕所需时间
 87     [self performSelector:@selector(enterScreen) withObject:nil afterDelay:enterDuration];
 88     
 89     
 90     
 91     //v = s/t   时间相同,弹幕越长,速度越快
 92     __block CGRect frame = self.frame;
 93     [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
 94         frame.origin.x = -wholeWidth;
 95         self.frame = frame;
 96     } completion:^(BOOL finished) {
 97         [self removeFromSuperview] ;
 98     
 99 //        回调状态
100         if(self.moveStatusBlock){
101             self.moveStatusBlock(End);
102         }
103         
104     }];
105     
106     
107 }
108 
109 //MARK:结束动画
110 -(void)stopAnimation{
111     [NSObject cancelPreviousPerformRequestsWithTarget:self];
112     [self.layer removeAllAnimations];
113     [self removeFromSuperview];
114 }
115 
116 //MARK: 弹幕完全入屏幕调用
117 -(void)enterScreen{
118     if(self.moveStatusBlock){
119         self.moveStatusBlock(Enter);
120     }
121 }
122 
123 @end
View Code

BulletManager.h

 1 //
 2 //  BulletManager.h
 3 //  danMu
 4 //
 5 //  Created by Shaoting Zhou on 2017/9/11.
 6 //  Copyright © 2017年 Shaoting Zhou. All rights reserved.
 7 //
 8 
 9 #import <Foundation/Foundation.h>
10 
11 @class BulletView;
12 @interface BulletManager : NSObject
13 
14 @property (nonatomic,copy) void(^generateViewBlock)(BulletView* view);
15 
16 -(void)start;
17 -(void)stop;
18 -(void)createBulletView:(NSDictionary *)commentDic trajectory:(int)trajectory;
19 
20 @end
View Code

BulletManager.m

  1 //
  2 //  BulletManager.m
  3 //  danMu
  4 //
  5 //  Created by Shaoting Zhou on 2017/9/11.
  6 //  Copyright © 2017年 Shaoting Zhou. All rights reserved.
  7 //
  8 
  9 #import "BulletManager.h"
 10 #import "BulletView.h"
 11 
 12 @interface BulletManager()
 13 @property (nonatomic,strong) NSMutableArray * datasource;   //弹幕数据源
 14 @property (nonatomic,strong) NSMutableArray * bulletComments;  //弹幕使用过程中的数组变量
 15 @property (nonatomic,strong) NSMutableArray * bulletViews;  //存放弹幕view的数组变量
 16 @property BOOL stopAnimation;  //动画结束标示
 17 @end
 18 
 19 @implementation BulletManager
 20 
 21 -(instancetype)init{
 22     if(self = [super init]){
 23         self.stopAnimation = YES;
 24     }
 25     return self;
 26 }
 27 
 28 -(void)start{
 29     if(!self.stopAnimation){
 30         return;
 31     }
 32     self.stopAnimation = NO;
 33     [self.bulletComments removeAllObjects];
 34     [self.bulletComments addObjectsFromArray:self.datasource];
 35     
 36     [self initBulletComment];
 37 
 38 }
 39 
 40 -(void)stop{
 41     if(self.stopAnimation){
 42         return;
 43     }
 44     self.stopAnimation = YES;
 45     
 46     [self.bulletViews enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
 47         BulletView * view = obj;
 48         [view stopAnimation];
 49         view = nil;
 50     }];
 51     [self.bulletViews removeAllObjects];
 52 }
 53 
 54 
 55 //MARK:初始化弹幕,随机分配弹幕轨迹
 56 -(void)initBulletComment{
 57     NSMutableArray *  trajectorys = [NSMutableArray arrayWithArray:@[@(0),@(1),@(2),@(3)]];
 58     for (int i = 0; i < 4; i++) {
 59         if(self.bulletComments.count > 0){
 60             //        通过随机数获取弹幕轨迹
 61             NSInteger  index = arc4random()%trajectorys.count;
 62             int trajectory = [[trajectorys objectAtIndex:index] intValue];
 63             [trajectorys removeObjectAtIndex:index];
 64             
 65             //    从弹幕数组中取出弹幕数据
 66             NSDictionary * commentDic = [self.bulletComments firstObject];
 67             [self.bulletComments removeObjectAtIndex:0];
 68             
 69             [self createBulletView:commentDic trajectory:trajectory];
 70         }
 71 
 72     }
 73     
 74 }
 75 
 76 
 77 
 78 //MARK: 创建弹幕视图
 79 -(void)createBulletView:(NSDictionary *)commentDic trajectory:(int)trajectory {
 80     if(self.stopAnimation){
 81         return;
 82     }
 83     BulletView * bulletView = [[BulletView alloc]initWithCommentDic:commentDic];
 84 //    NSLog(@"%@",commentDic);
 85     bulletView.trajectory = trajectory;
 86     [self.bulletViews addObject:bulletView];
 87     
 88     __weak typeof (bulletView) weakView = bulletView;
 89     __weak typeof(self) weakSelf = self;
 90     bulletView.moveStatusBlock = ^(MoveStatus status){
 91         if(weakSelf.stopAnimation){
 92             return;
 93         }
 94         
 95         switch (status) {
 96             case Start:{
 97                 //                弹幕开始,将view加入到弹幕管理的变量bulletViews中
 98                 [weakSelf.bulletViews addObject:weakView];
 99                 break;
100             }
101             case Enter:{
102                 //                弹幕完全进入屏幕,判断是否还有弹幕,有的话则在该弹幕轨迹中创建弹幕视图
103                 NSDictionary * commentDic = [self nextComment];
104                 if(commentDic){
105                     [weakSelf createBulletView:commentDic trajectory:trajectory];  //递归即可
106                 }
107                 break;
108             }
109             case End:{
110 //            弹幕飞出屏幕后,从bulletViews删除,移除资源
111                 if([weakSelf.bulletViews containsObject:weakView]){
112                     [weakView stopAnimation];
113                     [weakSelf.bulletViews removeObject:weakView];
114                 }
115                 //已经木有弹幕了,循环播放
116                 if(weakSelf.bulletViews.count == 0){
117                     self.stopAnimation = YES;
118                     [weakSelf start];
119                 }
120                 break;
121             }
122             default:
123                 break;
124         }
125 
126     };
127     
128 //    回调view给viewControlller
129     if(self.generateViewBlock){
130         self.generateViewBlock(bulletView);
131     }
132     
133 }
134 
135 //MARK:  取出下一条弹幕
136 -(NSDictionary *)nextComment{
137     NSDictionary * commentDic = [self.bulletComments firstObject];
138     if(commentDic){
139         [self.bulletComments removeObjectAtIndex:0];
140     }
141     return commentDic;
142 }
143 
144 -(NSMutableArray *)datasource{
145     if(!_datasource){
146         NSData * data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"data" ofType:@"json"]];
147         NSArray * ary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
148         self.datasource = [NSMutableArray arrayWithArray:ary];
149     }
150     return _datasource;
151 }
152 
153 -(NSMutableArray *)bulletComments{
154     if(!_bulletComments){
155         self.bulletComments = [NSMutableArray array];
156     }
157     return _bulletComments;
158 }
159 
160 -(NSMutableArray *)bulletViews{
161     if(!_bulletViews){
162         self.bulletViews = [NSMutableArray array];
163     }
164     return _bulletViews;
165 }
166 
167 
168 @end
View Code

数据源:

[
 {
   "userPhoto":"https://ws1.sinaimg.cn/large/610dc034ly1fiz4ar9pq8j20u010xtbk.jpg",
   "danmu":"城市套路深,我要回农村!!!"
 },
 {
    "userPhoto":"https://ws1.sinaimg.cn/large/610dc034ly1fis7dvesn6j20u00u0jt4.jpg",
    "danmu":"农村路更滑,人心更复杂~~"
 },
 {
 "userPhoto":"https://ws1.sinaimg.cn/large/610dc034ly1fiiiyfcjdoj20u00u0ju0.jpg",
 "danmu":"6666666666666"
 },
 {
 "userPhoto":"https://ws1.sinaimg.cn/large/610dc034gy1fi2okd7dtjj20u011h40b.jpg",
 "danmu":"要死,要死,要死~~~~~~~~~~~~~~~~~~"
 },
 {
 "userPhoto":"http://ww1.sinaimg.cn/large/610dc034ly1fhyeyv5qwkj20u00u0q56.jpg",
 "danmu":"前方高能预警"
 },
 {
 "userPhoto":"http://ww3.sinaimg.cn/large/610dc034jw1f5d36vpqyuj20zk0qo7fc.jpg",
 "danmu":"剧透死全家"
 },
 {
 "userPhoto":"http://7xi8d6.com1.z0.glb.clouddn.com/2017-03-07-003645.jpg",
 "danmu":"我是迟到的freestyle"
 },
 {
 "userPhoto":"http://ww1.sinaimg.cn/large/610dc034ly1fhyeyv5qwkj20u00u0q56.jpg",
 "danmu":"这个碗又大又圆,就像这个剧又污又刺激"
 },
 {
 "userPhoto":"http://ww1.sinaimg.cn/large/610dc034ly1fhyeyv5qwkj20u00u0q56.jpg",
 "danmu":"哈哈哈哈."
 },
 {
 "userPhoto":"https://ws1.sinaimg.cn/large/610dc034ly1fiz4ar9pq8j20u010xtbk.jpg",
 "danmu":"

以上是关于iOS 弹幕制作的主要内容,如果未能解决你的问题,请参考以下文章

《隐秘的角落》弹幕分析,制作词云,看看观众们对该剧的评价如何

哇哦,弹幕居然是这么弄出来的!一文学会如何用js制作一个弹幕效果

哇哦,弹幕居然是这么弄出来的!一文学会如何用js制作一个弹幕效果

js实现视频发送弹幕文字可暂停

又一枚精彩的弹幕效果jQuery实现

弹幕游戏子弹模式(占坑待续)