彈幕制作
一、需求分析:
1.首先計(jì)算在指定區(qū)域你需要幾行彈幕
2.對(duì)使用過的label進(jìn)行緩存
3.每行彈幕進(jìn)入屏幕多少展运、這一行就可以進(jìn)入下一條彈幕坎弯。
4.如果這一行彈幕滿了就從第二行彈幕開始.以此類推。
5.如果最后所有行都滿了則加快彈幕播放速度
7.對(duì)彈幕上的文字以及圖片點(diǎn)擊時(shí)手勢(shì)的識(shí)別和添加
6.此外如果彈幕中有圖片頭像這些需要提前緩存下載数焊、以及其它性能優(yōu)化當(dāng)然本案列不會(huì)寫這么多辛润。
二膨处、設(shè)計(jì)如下:
總體采用面向?qū)ο蟮姆绞竭M(jìn)行邏輯的劃分、案列使用三行彈幕以隊(duì)列的方式保存每條消息的進(jìn)入與銷毀频蛔、通過字典保存每一行的狀態(tài)方便查找空閑行灵迫、每條彈幕內(nèi)部使用路徑layer動(dòng)畫實(shí)現(xiàn)layer移動(dòng)秦叛、通過系統(tǒng)定時(shí)器CADisplayLink記錄位置變化和改變狀態(tài).
1晦溪、重要類如下:
BarrageLabel 用來顯示具體彈幕內(nèi)容以及執(zhí)行內(nèi)部的動(dòng)畫等
BarrageLine 記錄某一行是否處于空閑狀態(tài)
BarrageVC 控制業(yè)務(wù)邏輯
如需擴(kuò)展建議添加一些消息類以及消息管理類。
2挣跋、重要屬性如下
//負(fù)責(zé)消息進(jìn)入與取出
@property(nonatomic, strong) NSMutableArray <NSMutableAttributedString *>* textQueue;
//保存當(dāng)前行狀態(tài)
@property(nonatomic, strong) NSMutableDictionary <NSNumber *, BarrageLine*>*barrageLineDic;
//緩存所有已創(chuàng)建的的label
@property(nonatomic, strong) NSMutableArray <BarrageLabel *>* totalLabels;
//正在屏幕上顯示的label
@property(nonatomic, strong) NSMutableArray<BarrageLabel *>* currentUsingLabels;
//目前處于空閑中未被使用的label
@property(nonatomic, strong) NSMutableArray<BarrageLabel *>* currentIdleLabels;
三三圆、代碼講解
1、N條假數(shù)據(jù)消息避咆,富文本實(shí)現(xiàn)圖文混排舟肉。并開啟啟動(dòng)定時(shí)器。由于彈幕目前是下一條緊跟上一條查库、如果某段時(shí)間沒有消息但是突然推送過來了消息將無法啟動(dòng)下一條繼續(xù)進(jìn)入加了此定時(shí)器幾秒檢測(cè)一次路媚,當(dāng)然你也可以其它優(yōu)化方案。
-(void)loadData
{
for (int i =0 ; i< 10; i++) {
NSMutableAttributedString *text = [NSMutableAttributedString new];
//添加圖片
NSTextAttachment *attchment = [[NSTextAttachment alloc]init];
UIImage *image = [UIImage imageNamed:BBDefaultHeder];
attchment.image = image;
// 設(shè)置圖片大小
attchment.bounds = CGRectMake(0, 0, 20, 20);
NSAttributedString *stringImage = [NSAttributedString attributedStringWithAttachment:attchment];
[text appendAttributedString:stringImage];
// [text insertAttributedString:attachment atIndex:2];
NSString *contentString = @"hello i come frome china.";
NSMutableAttributedString *stingAttri = [[NSMutableAttributedString alloc]initWithString:contentString];
//設(shè)置這一行向上移動(dòng)5
[stingAttri addAttribute:NSBaselineOffsetAttributeName value:@(5) range:NSMakeRange(0, contentString.length)];
[text appendAttributedString:stingAttri];
//再添加一個(gè)圖片
[text appendAttributedString:stringImage];
//加入隊(duì)列
[self.textQueue addObject:text];
}
//開啟彈幕添加定時(shí)器
[self.barrageTimer setFireDate:[NSDate distantPast]];
}
2樊销、對(duì)屏幕上的消息存入進(jìn)入緩存池整慎、目前一般長度的消息親測(cè)8個(gè)label以內(nèi)脏款。消息越短需要緩存的將會(huì)多些.
//添加一條彈幕
-(void)addABarrageUI
{
if (self.textQueue.count == 0) {
//沒有足夠的文字了--不需要顯示
NSLog(@"Barrage- not enough text.!");
//停掉定時(shí)器
[self.barrageTimer setFireDate:[NSDate distantPast]];
return;
}
int idleLine = [self getIdelLine];
if (idleLine == -1) {
//沒有空閑的行 ----進(jìn)行等待
NSLog(@"Barrage- no enough idle line.!");
return;
}
NSLog(@"Barrage- will add a barrage.");
NSAttributedString *text = [self.textQueue firstObject];
BarrageLabel *label;
//計(jì)算文字的寬
CGSize size0 = [self calculationTextSize:text.string cgSize:CGSizeMake(CGFLOAT_MAX, 30) font:16.0];//206.28
CGFloat width =size0.width+20*2;//20是圖片的寬
if (self.currentIdleLabels.count >0) {
//有空閑緩存的label直接拿來使用
label = [self.currentIdleLabels firstObject];
NSLog(@"Barrage- get a cache idle label. count=%lu",self.currentIdleLabels.count);
}else{
//沒有空閑緩存的label-重新創(chuàng)建
label = [[BarrageLabel alloc]initWithFrame:CGRectMake(-width, 0, width, 30)];
[self.view addSubview:label];
[self.totalLabels addObject:label];
}
label.frame = CGRectMake(-width, 0, width, 30);//更新尺寸寬
label.attributedText = text;
// label.backgroundColor = [UIColor greenColor];
label.delegete = self;
[self.textQueue removeObjectAtIndex:0];
//開始動(dòng)畫
[label startAnimationAtLine:idleLine];
}
3.代理實(shí)現(xiàn)label狀態(tài)回調(diào)記錄空閑行狀態(tài)變化、以及記錄可用label變化
-(void)visibleDidChange:(BarrageLabel *)label
{
// NSLog(@"visibleDidChange");
if (label.currentVisibleType == BarrageLabelVisibleTypeTailInScreen) {
//可以添加標(biāo)記多了一個(gè)空閑位置
BarrageLine *line = [self.barrageLineDic objectForKey:@(label.currentLine)];
line.currentStatus = BarrageLineStatusIdle;
if (self.isRuning) {
//添加一個(gè)新的彈幕進(jìn)來了
[self addABarrageUI];
}
}
}
-(void)stauesDidChange:(BarrageLabel *)label
{
// NSLog(@"stauesDidChange");
if (label.currentStatus != BarrageLabelStatusUsing) {
//此label處于空閑狀態(tài)
if ([self.currentUsingLabels containsObject:label]) {
[self.currentUsingLabels removeObject:label];
}
if (self.currentIdleLabels.count > 20) {
//開始清除多余的緩存label
}
}else{
//此label正在被使用
if (![self.currentUsingLabels containsObject:label]) {
[self.currentUsingLabels addObject:label];
NSLog(@"Barrage- current using label count is =%lu",self.currentUsingLabels.count);
}
}
}
4.移動(dòng)動(dòng)畫實(shí)現(xiàn)
UIBezierPath *path = [self creatPathPoints:array];
//創(chuàng)建動(dòng)畫
CAAnimation *animation = [self creatAnimationPath:path];
//開始動(dòng)畫
[self.layer addAnimation:animation forKey:@"LLAnimationPosition"];
-(UIBezierPath *)creatPathPoints:(NSArray <NSValue *>*)pointValues
{
UIBezierPath* path = [UIBezierPath bezierPath];
path.lineWidth = 1.0;
path.lineCapStyle = kCGLineCapRound; //線條拐角
path.lineJoinStyle = kCGLineJoinRound; //終點(diǎn)處理
for (int i = 0; i<pointValues.count; i++) {
if (i==0) {
//起點(diǎn)
[path moveToPoint:pointValues[i].CGPointValue];
}else{
//連線
[path addLineToPoint:pointValues[i].CGPointValue];
}
}
return path;
}
-(CAKeyframeAnimation *)creatAnimationPath:(UIBezierPath *)path
{
//添加動(dòng)畫
CAKeyframeAnimation * animation;
animation=[CAKeyframeAnimation animationWithKeyPath:@"position"];
animation.path = path.CGPath;
animation.duration = 8.0;
animation.repeatCount=0;
// 結(jié)束保持最后狀態(tài)
// animation.fillMode = kCAFillModeForwards;
//線性
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
[animation setDelegate:self];
//動(dòng)畫執(zhí)行完不移除和fillmode都要設(shè)置
// [animation setRemovedOnCompletion:NO];
return animation;
}
源碼
有疑問的小伙伴歡迎加交流討論QQ:206931384