iOS 弹幕制作
离职的最后一天,在公司学习下弹幕的制作.基于OC.
主要思路:
1.首先建一个弹幕类BulletView,基于UIView,然后在该类上写个UIlabel,用于放置弹幕文字,然后前端放置一个UIImageView,放置用户头像.该类主要绘制UI和动画.
2.其次建立一个弹幕的管理类BulletManager,主要管理弹幕数据源,随机分配弹幕轨迹,根据不同状态(start,enter,end)做不同处理,该类主要负责逻辑部分.
其中,在弹幕类BulletView中写一个回调,负责回调当前弹幕的状态(start,enter,end)给管理类BulletManager;在管理类BulletManage写一个回调,负责回调弹幕视图给ViewController.
弹幕类:
BulletView.h
//
// BulletView.h
// danMu
//
// Created by Shaoting Zhou on 2017/9/11.
// Copyright © 2017年 Shaoting Zhou. All rights reserved.
// #import <UIKit/UIKit.h>
typedef NS_ENUM(NSInteger,MoveStatus){
Start,
Enter,
End,
};
@interface BulletView : UIView
@property (nonatomic,assign) int trajectory; //弹幕弹道
@property (nonatomic,copy) void(^ moveStatusBlock)(MoveStatus status); //弹幕状态回调 开始 运行中 结束 -(instancetype)initWithCommentDic:(NSDictionary *)dic; //初始化弹幕 -(void)startAnimation; //开始动画
-(void)stopAnimation; //结束动画 @end
BulletView.m
//
// BulletView.m
// danMu
//
// Created by Shaoting Zhou on 2017/9/11.
// Copyright © 2017年 Shaoting Zhou. All rights reserved.
// #import "BulletView.h" #define padding 10
#define imgHeight 30
@interface BulletView()
@property (nonatomic,strong) UILabel * lbComment;
@property (nonatomic,strong) UIImageView * imgView; @end @implementation BulletView //MARK: 初始化弹幕
-(instancetype)initWithCommentDic:(NSDictionary *)dic{
if(self = [super init]){
self.layer.cornerRadius = /; CGFloat colorR = arc4random()%;
CGFloat colorG = arc4random()%;
CGFloat colorB = arc4random()%;
self.backgroundColor = [UIColor colorWithRed:colorR/ green:colorG/ blue:colorB/ alpha:1.0]; //计算弹幕的实际宽度
NSDictionary *attr = @{NSFontAttributeName:[UIFont systemFontOfSize:]};
NSString * comment = dic[@"danmu"];
CGFloat width = [comment sizeWithAttributes:attr].width;
self.bounds = CGRectMake(, , width + * padding + imgHeight , );
self.lbComment.text = comment;
self.lbComment.frame = CGRectMake(padding + imgHeight, , width, ); //头像
self.imgView.frame = CGRectMake(-padding, -padding, imgHeight + padding, imgHeight + padding);
self.imgView.layer.cornerRadius = (imgHeight + padding)/;
NSURL * url = [NSURL URLWithString:dic[@"userPhoto"]];
self.imgView.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];; // NSLog(@"%@",comment);
}
return self;
} -(UILabel *)lbComment{
if(!_lbComment){
self.lbComment = [[UILabel alloc]initWithFrame:CGRectZero];
self.lbComment.font = [UIFont systemFontOfSize:];
self.lbComment.textColor = [UIColor whiteColor];
self.lbComment.textAlignment = NSTextAlignmentCenter;
[self addSubview:self.lbComment]; }
return _lbComment;
} -(UIImageView *)imgView{
if(!_imgView){
self.imgView = [UIImageView new];
self.imgView.clipsToBounds = YES;
self.imgView.contentMode = UIViewContentModeScaleAspectFill;
[self addSubview:self.imgView];
}
return _imgView;
} //MARK:开始动画
-(void)startAnimation{
CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
CGFloat duration = 4.0f;
CGFloat wholeWidth = screenWidth + CGRectGetWidth(self.bounds); // 弹幕开始
if(self.moveStatusBlock){
self.moveStatusBlock(Start);
} CGFloat speed = wholeWidth/duration; // v = s/t
CGFloat enterDuration = CGRectGetWidth(self.bounds)/speed; //完全进入屏幕所需时间
[self performSelector:@selector(enterScreen) withObject:nil afterDelay:enterDuration]; //v = s/t 时间相同,弹幕越长,速度越快
__block CGRect frame = self.frame;
[UIView animateWithDuration:duration delay: options:UIViewAnimationOptionCurveLinear animations:^{
frame.origin.x = -wholeWidth;
self.frame = frame;
} completion:^(BOOL finished) {
[self removeFromSuperview] ; // 回调状态
if(self.moveStatusBlock){
self.moveStatusBlock(End);
} }]; } //MARK:结束动画
-(void)stopAnimation{
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[self.layer removeAllAnimations];
[self removeFromSuperview];
} //MARK: 弹幕完全入屏幕调用
-(void)enterScreen{
if(self.moveStatusBlock){
self.moveStatusBlock(Enter);
}
} @end
BulletManager.h
//
// BulletManager.h
// danMu
//
// Created by Shaoting Zhou on 2017/9/11.
// Copyright © 2017年 Shaoting Zhou. All rights reserved.
// #import <Foundation/Foundation.h> @class BulletView;
@interface BulletManager : NSObject @property (nonatomic,copy) void(^generateViewBlock)(BulletView* view); -(void)start;
-(void)stop;
-(void)createBulletView:(NSDictionary *)commentDic trajectory:(int)trajectory; @end
BulletManager.m
//
// BulletManager.m
// danMu
//
// Created by Shaoting Zhou on 2017/9/11.
// Copyright © 2017年 Shaoting Zhou. All rights reserved.
// #import "BulletManager.h"
#import "BulletView.h" @interface BulletManager()
@property (nonatomic,strong) NSMutableArray * datasource; //弹幕数据源
@property (nonatomic,strong) NSMutableArray * bulletComments; //弹幕使用过程中的数组变量
@property (nonatomic,strong) NSMutableArray * bulletViews; //存放弹幕view的数组变量
@property BOOL stopAnimation; //动画结束标示
@end @implementation BulletManager -(instancetype)init{
if(self = [super init]){
self.stopAnimation = YES;
}
return self;
} -(void)start{
if(!self.stopAnimation){
return;
}
self.stopAnimation = NO;
[self.bulletComments removeAllObjects];
[self.bulletComments addObjectsFromArray:self.datasource]; [self initBulletComment]; } -(void)stop{
if(self.stopAnimation){
return;
}
self.stopAnimation = YES; [self.bulletViews enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
BulletView * view = obj;
[view stopAnimation];
view = nil;
}];
[self.bulletViews removeAllObjects];
} //MARK:初始化弹幕,随机分配弹幕轨迹
-(void)initBulletComment{
NSMutableArray * trajectorys = [NSMutableArray arrayWithArray:@[@(),@(),@(),@()]];
for (int i = ; i < ; i++) {
if(self.bulletComments.count > ){
// 通过随机数获取弹幕轨迹
NSInteger index = arc4random()%trajectorys.count;
int trajectory = [[trajectorys objectAtIndex:index] intValue];
[trajectorys removeObjectAtIndex:index]; // 从弹幕数组中取出弹幕数据
NSDictionary * commentDic = [self.bulletComments firstObject];
[self.bulletComments removeObjectAtIndex:]; [self createBulletView:commentDic trajectory:trajectory];
} } } //MARK: 创建弹幕视图
-(void)createBulletView:(NSDictionary *)commentDic trajectory:(int)trajectory {
if(self.stopAnimation){
return;
}
BulletView * bulletView = [[BulletView alloc]initWithCommentDic:commentDic];
// NSLog(@"%@",commentDic);
bulletView.trajectory = trajectory;
[self.bulletViews addObject:bulletView]; __weak typeof (bulletView) weakView = bulletView;
__weak typeof(self) weakSelf = self;
bulletView.moveStatusBlock = ^(MoveStatus status){
if(weakSelf.stopAnimation){
return;
} switch (status) {
case Start:{
// 弹幕开始,将view加入到弹幕管理的变量bulletViews中
[weakSelf.bulletViews addObject:weakView];
break;
}
case Enter:{
// 弹幕完全进入屏幕,判断是否还有弹幕,有的话则在该弹幕轨迹中创建弹幕视图
NSDictionary * commentDic = [self nextComment];
if(commentDic){
[weakSelf createBulletView:commentDic trajectory:trajectory]; //递归即可
}
break;
}
case End:{
// 弹幕飞出屏幕后,从bulletViews删除,移除资源
if([weakSelf.bulletViews containsObject:weakView]){
[weakView stopAnimation];
[weakSelf.bulletViews removeObject:weakView];
}
//已经木有弹幕了,循环播放
if(weakSelf.bulletViews.count == ){
self.stopAnimation = YES;
[weakSelf start];
}
break;
}
default:
break;
} }; // 回调view给viewControlller
if(self.generateViewBlock){
self.generateViewBlock(bulletView);
} } //MARK: 取出下一条弹幕
-(NSDictionary *)nextComment{
NSDictionary * commentDic = [self.bulletComments firstObject];
if(commentDic){
[self.bulletComments removeObjectAtIndex:];
}
return commentDic;
} -(NSMutableArray *)datasource{
if(!_datasource){
NSData * data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"data" ofType:@"json"]];
NSArray * ary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
self.datasource = [NSMutableArray arrayWithArray:ary];
}
return _datasource;
} -(NSMutableArray *)bulletComments{
if(!_bulletComments){
self.bulletComments = [NSMutableArray array];
}
return _bulletComments;
} -(NSMutableArray *)bulletViews{
if(!_bulletViews){
self.bulletViews = [NSMutableArray array];
}
return _bulletViews;
} @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":""
},
{
"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 弹幕制作的更多相关文章
- iOS XCode7制作.Framework动态库和.a静态库的总结
一.开发SDK时的支持情况: OC语言制作动态库时,支持iOS8+:OC语言制作静态库,支持iOS7+. Swift语言制作动态库时,支持iOS8+;Swift不支持静态库. 对于SDK来说,支持情况 ...
- 游戏制作之路:一个对我来说可实现的High-end的Mac/iOS游戏制作大概计划
对于学习一些东西,我比较习惯任务驱动式的学习,也就是说,要事先订好一个目标,要做什么东西,达到什么效果,然后根据自己了解的知识作一个可以实现这个目标的计划. 现在要学的是游戏制作,而且是High-en ...
- iOS,Xcode7 制作Framework,含资源和界面
Xcode7 制作Framework 本文通过Demo方式介绍1)将含bundle和存代码编写界面打包进framework:2)将storyboard +assets.xcassets打包. (一) ...
- iOS:插件制作入门
本文将介绍创建一个Xcode4插件所需要的基本步骤以及一些常用的方法.请注意为Xcode创建插件并没有任何的官方支持,因此本文所描述的方法和提供的信息可能会随Apple在Xcode上做的变化而失效.另 ...
- 笔记-iOS弹幕(源码)实现原理解析
最近,读完今年的第三本书<大话移动APP测试 Android与iOS>,在读到陈晔前辈改变中国测试行业的决心时,内心无比激动,作为一名初生的开发人员,我可能还无法理解测试行业的本质,但他那 ...
- 【转】iOS弹幕库OCBarrage-如何hold住每秒5000条巨量弹幕
最近公司做新需求, 原来用的老弹幕库, 已经无法满足需要. 迫不得已自己写了一套弹幕库OCBarrage. 这套弹幕库轻量, 可拓展, 高度自定义, 超高性能, 简单易上手. 无论哪家公司软件的性能绝 ...
- ios证书制作与上架指南
项目开发完了,要上架 ios AppStore 记录一下经过,以及需要提前准备和预防的东西,以便下次省心! 一.首先要申请开发者账号: 账号按流程注册申请,当时申请了够10遍,总结以下经验: 1.申请 ...
- iOS Framework制作流程
1.新建工程选择iOS —> Cocoa Touch Framework 2.进入创建好的工程删除掉自带的工程同名头文件 3.添加所需文件 4.TARGETS —> Build Setti ...
- ios学习-制作一个浏览图片的Demo
一.项目要求:制作一个浏览图片的Demo,要求包含夜间模式,以及改变图片大小,能够显示不同的图片描述 二.开发步骤: 1.在storyboard上添加一个空白的View,然后添加”设置“按钮,添加im ...
随机推荐
- esp32-micropython
本来之前买和另一贴子的esp8266一起买了一块esp32. 现在开发esp的大概有乐鑫的ide以及基于乐鑫定制的.arduino.nodemcu.还有就是现在要讲的micropython. 乐鑫的主 ...
- opencv学习之路(23)、轮廓查找与绘制(二)——访问轮廓每个点
一.简介 二.画出每个轮廓的每个点 #include "opencv2/opencv.hpp" using namespace cv; void main() { Mat src= ...
- Win10在右键菜单添加在此处打开命令窗口项
转载:https://jingyan.baidu.com/article/2d5afd6930107a85a2e28e2d.html 第一步:新建一个文本文档,输入如下代码,然后另存为OpenCmdH ...
- Memcache_分布式缓存
一. Memcache简介 1. 什么要用到Memcache以及该能解决什么问题 高并发访问数据库的痛楚:死锁! 磁盘IO之痛 多客户端共同缓存 NET+Memory>>IO 读写性能完美 ...
- guxh的python笔记二:函数基础
1,函数的参数 1.1,查看函数的参数类型 def run(a, *args, b, **kwargs): return a + b 可以通过如下方式查看参数类型: import inspect k ...
- ionic调用手机系统的拨打电话
android调用如下: 在config.xml中添加 <access origin="tel:*" launch-external="yes" /> ...
- Annotaion——深入理解注解类型
什么是注解? 对于很多初次接触的开发者来说应该都有这个疑问?Annontation是Java5开始引入的新特征,中文名称叫注解.它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metada ...
- Caffarelli 关于自由边界正则性的论文C1
Caffarelli 关于自由边界正则性的论文 接下来主要想叙述一下Caffarelli的C1文章中的一些想法,这是最近这几天看的文献. 对于从自由边界的Lipschitz正则性到$C^{1,\alp ...
- 『PyTorch × TensorFlow』第十七弹_ResNet快速实现
『TensorFlow』读书笔记_ResNet_V2 对比之前的复杂版本,这次的torch实现其实简单了不少,不过这和上面的代码实现逻辑过于复杂也有关系. 一.PyTorch实现 # Author : ...
- 『TensorFlow』网络操作API_中_损失函数及分类器
一.误差值 度量两个张量或者一个张量和零之间的损失误差,这个可用于在一个回归任务或者用于正则的目的(权重衰减). l2_loss tf.nn.l2_loss(t, name=None) 解释:这个函数 ...