离职的最后一天,在公司学习下弹幕的制作.基于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
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
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
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
数据源:
[ { "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":"💪💪💪我的麒麟臂又发作" } ]
OK,上面就是弹幕的模块代码,接下来就可以把弹幕放置在任何你需要的地方了,如;列表.视频,音频,网页等.
然后,在ViewController中使用该弹幕即可.如下,写三个按钮(开始弹幕,结束弹幕,增加一条弹幕),然后写调用代码即可.
1 // 2 // ViewController.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 "ViewController.h" 10 #import "BulletManager.h" 11 #import "BulletView.h" 12 @interface ViewController () 13 @property (nonatomic,strong) BulletManager * manager; 14 @property (nonatomic,strong) UITextField * text; 15 @end 16 17 @implementation ViewController 18 19 - (void)viewDidLoad { 20 [super viewDidLoad]; 21 self.manager = [[BulletManager alloc]init]; 22 __weak typeof(self) weakSelf = self; 23 self.manager.generateViewBlock = ^(BulletView *view) { 24 [weakSelf addBulletView:view]; 25 }; 26 CGFloat width = [UIScreen mainScreen].bounds.size.width; 27 28 UIButton * startBtn = [[UIButton alloc]initWithFrame:CGRectMake(0, 100, width/3, 40)]; 29 [startBtn setTitleColor:[UIColor redColor] forState:UIControlStateNormal]; 30 [startBtn setTitle:@"开始" forState:UIControlStateNormal]; 31 [startBtn addTarget:self action:@selector(startAction) forControlEvents:UIControlEventTouchUpInside]; 32 [self.view addSubview:startBtn]; 33 34 UIButton * endBtn = [[UIButton alloc]initWithFrame:CGRectMake(width/3, 100, width/3, 40)]; 35 [endBtn setTitleColor:[UIColor redColor] forState:UIControlStateNormal]; 36 [endBtn setTitle:@"结束" forState:UIControlStateNormal]; 37 [endBtn addTarget:self action:@selector(endAction) forControlEvents:UIControlEventTouchUpInside]; 38 [self.view addSubview:endBtn]; 39 40 UIButton * addDanmu = [[UIButton alloc]initWithFrame:CGRectMake(width * 2/3, 100, width/3, 40)]; 41 [addDanmu setTitleColor:[UIColor redColor] forState:UIControlStateNormal]; 42 [addDanmu setTitle:@"增加" forState:UIControlStateNormal]; 43 [addDanmu addTarget:self action:@selector(addDanmuAction) forControlEvents:UIControlEventTouchUpInside]; 44 [self.view addSubview:addDanmu]; 45 46 self.text = [[UITextField alloc]initWithFrame:CGRectMake(0, 150, width, 40)]; 47 [self.text setBorderStyle:UITextBorderStyleLine]; 48 self.text.placeholder = @"增加一条弹幕"; 49 [self.view addSubview:self.text]; 50 51 // Do any additional setup after loading the view, typically from a nib. 52 } 53 54 -(void)startAction{ 55 [self.manager start]; 56 } 57 58 -(void)endAction{ 59 [self.manager stop]; 60 } 61 62 -(void)addDanmuAction{ 63 64 NSDictionary * dic = @{@"userPhoto":@"https://ws1.sinaimg.cn/large/610dc034ly1fiz4ar9pq8j20u010xtbk.jpg",@"danmu":self.text.text}; 65 [self.manager createBulletView:dic trajectory:0]; 66 67 } 68 69 -(void)addBulletView:(BulletView *)view{ 70 CGFloat width = [UIScreen mainScreen].bounds.size.width; 71 view.frame = CGRectMake(width, 300 + view.trajectory * 60, CGRectGetWidth(view.bounds), CGRectGetHeight(view.bounds)); 72 [self.view addSubview:view]; 73 74 [view startAnimation]; 75 76 } 77 78 - (void)didReceiveMemoryWarning { 79 [super didReceiveMemoryWarning]; 80 // Dispose of any resources that can be recreated. 81 } 82 83 84 @end
截图如下:
github: https://github.com/pheromone/danMu
感谢慕课网