天天看點

iOS 動畫系列二(彈幕制作)

彈幕制作

iOS 動畫系列二(彈幕制作)
一、需求分析:

1.首先計算在指定區域你需要幾行彈幕

2.對使用過的label進行緩存

3.每行彈幕進入螢幕多少、這一行就可以進入下一條彈幕。

4.如果這一行彈幕滿了就從第二行彈幕開始.以此類推。

5.如果最後所有行都滿了則加快彈幕播放速度

7.對彈幕上的文字以及圖檔點選時手勢的識别和添加

6.此外如果彈幕中有圖檔頭像這些需要提前緩存下載下傳、以及其它性能優化當然本案列不會寫這麼多。

二、設計如下:

總體采用面向對象的方式進行邏輯的劃分、案列使用三行彈幕以隊列的方式儲存每條消息的進入與銷毀、通過字典儲存每一行的狀态友善查找空閑行、每條彈幕内部使用路徑layer動畫實作layer移動、通過系統定時器CADisplayLink記錄位置變化和改變狀态.

1、重要類如下:

BarrageLabel 用來顯示具體彈幕内容以及執行内部的動畫等

BarrageLine 記錄某一行是否處于空閑狀态

BarrageVC 控制業務邏輯

如需擴充建議添加一些消息類以及消息管理類。

2、重要屬性如下

//負責消息進入與取出
@property(nonatomic, strong) NSMutableArray <NSMutableAttributedString *>* textQueue;
 //儲存目前行狀态
@property(nonatomic, strong) NSMutableDictionary <NSNumber *, BarrageLine*>*barrageLineDic;
 //緩存所有已建立的的label
@property(nonatomic, strong) NSMutableArray <BarrageLabel *>* totalLabels;
 //正在螢幕上顯示的label
 @property(nonatomic, strong) NSMutableArray<BarrageLabel *>* currentUsingLabels;
 //目前處于空閑中未被使用的label
@property(nonatomic, strong) NSMutableArray<BarrageLabel *>* currentIdleLabels;
           
三、代碼講解

1、N條假資料消息,富文本實作圖文混排。并開啟啟動定時器。由于彈幕目前是下一條緊跟上一條、如果某段時間沒有消息但是突然推送過來了消息将無法啟動下一條繼續進入加了此定時器幾秒檢測一次,當然你也可以其它優化方案。

-(void)loadData
{
    for (int i = ; i< ; i++) {
        NSMutableAttributedString *text = [NSMutableAttributedString new];

        //添加圖檔
        NSTextAttachment *attchment = [[NSTextAttachment alloc]init];
        UIImage *image = [UIImage imageNamed:BBDefaultHeder];
        attchment.image = image;
        // 設定圖檔大小
        attchment.bounds = CGRectMake(, , , );
        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];
        //設定這一行向上移動5
        [stingAttri addAttribute:NSBaselineOffsetAttributeName value:@() range:NSMakeRange(, contentString.length)];

        [text appendAttributedString:stingAttri];

        //再添加一個圖檔
        [text appendAttributedString:stringImage];

       //加入隊列
        [self.textQueue addObject:text];
    }
    //開啟彈幕添加定時器
    [self.barrageTimer setFireDate:[NSDate distantPast]];

}
           

2、對螢幕上的消息存入進入緩存池、目前一般長度的消息親測8個label以内。消息越短需要緩存的将會多些.

//添加一條彈幕
-(void)addABarrageUI
{
    if (self.textQueue.count == ) {
        //沒有足夠的文字了--不需要顯示
        NSLog(@"Barrage- not enough text.!");
        //停掉定時器
        [self.barrageTimer setFireDate:[NSDate distantPast]];
        return;
    }
    int idleLine = [self getIdelLine];
    if (idleLine == -) {
        //沒有空閑的行 ----進行等待
        NSLog(@"Barrage- no enough idle line.!");
        return;
    }
    NSLog(@"Barrage- will add a barrage.");
     NSAttributedString *text = [self.textQueue firstObject];
    BarrageLabel *label;
    //計算文字的寬
    CGSize size0 = [self calculationTextSize:text.string cgSize:CGSizeMake(CGFLOAT_MAX, ) font:];//206.28
    CGFloat width =size0.width+*;//20是圖檔的寬
        if (self.currentIdleLabels.count >) {
            //有空閑緩存的label直接拿來使用
            label = [self.currentIdleLabels firstObject];
            NSLog(@"Barrage- get a cache idle label. count=%lu",self.currentIdleLabels.count);
        }else{
            //沒有空閑緩存的label-重新建立
                label = [[BarrageLabel alloc]initWithFrame:CGRectMake(-width, , width, )];
                [self.view addSubview:label];
                [self.totalLabels addObject:label];
        }
    label.frame = CGRectMake(-width, , width, );//更新尺寸寬
    label.attributedText = text;
//    label.backgroundColor = [UIColor greenColor];
    label.delegete = self;
    [self.textQueue removeObjectAtIndex:];
    //開始動畫
    [label startAnimationAtLine:idleLine];
}
           

3.代理實作label狀态回調記錄空閑行狀态變化、以及記錄可用label變化

-(void)visibleDidChange:(BarrageLabel *)label
{
//    NSLog(@"visibleDidChange");
    if (label.currentVisibleType == BarrageLabelVisibleTypeTailInScreen) {
        //可以添加标記多了一個空閑位置
        BarrageLine *line = [self.barrageLineDic objectForKey:@(label.currentLine)];
        line.currentStatus = BarrageLineStatusIdle;
        if (self.isRuning) {
            //添加一個新的彈幕進來了
            [self addABarrageUI];
        }

    }
}

-(void)stauesDidChange:(BarrageLabel *)label
{
//    NSLog(@"stauesDidChange");
    if (label.currentStatus != BarrageLabelStatusUsing) {
        //此label處于空閑狀态
        if ([self.currentUsingLabels containsObject:label]) {
            [self.currentUsingLabels removeObject:label];
        }
        if (self.currentIdleLabels.count > ) {
            //開始清除多餘的緩存label
        }
    }else{
        //此label正在被使用
        if (![self.currentUsingLabels containsObject:label]) {
            [self.currentUsingLabels addObject:label];
            NSLog(@"Barrage- current using label count is =%lu",self.currentUsingLabels.count);

        }
    }

}
           

4.移動動畫實作

UIBezierPath *path = [self creatPathPoints:array];
    //建立動畫
 CAAnimation *animation = [self creatAnimationPath:path];
  //開始動畫
  [self.layer addAnimation:animation forKey:@"LLAnimationPosition"];

-(UIBezierPath *)creatPathPoints:(NSArray <NSValue *>*)pointValues
{
    UIBezierPath* path = [UIBezierPath bezierPath];
    path.lineWidth = ;
    path.lineCapStyle = kCGLineCapRound; //線條拐角
    path.lineJoinStyle = kCGLineJoinRound; //終點處理
    for (int i = ; i<pointValues.count; i++) {
        if (i==) {
            //起點
            [path moveToPoint:pointValues[i].CGPointValue];
        }else{
            //連線
            [path addLineToPoint:pointValues[i].CGPointValue];
        }
    }
    return path;
}

-(CAKeyframeAnimation *)creatAnimationPath:(UIBezierPath *)path
{
    //添加動畫
    CAKeyframeAnimation * animation;
    animation=[CAKeyframeAnimation animationWithKeyPath:@"position"];
    animation.path = path.CGPath;
    animation.duration = ;
    animation.repeatCount=;
    // 結束保持最後狀态
    //    animation.fillMode = kCAFillModeForwards;
    //線性
    [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
    [animation setDelegate:self];
    //動畫執行完不移除和fillmode都要設定
    //    [animation setRemovedOnCompletion:NO];
    return animation;
}
           

源碼

有疑問的小夥伴歡迎加交流讨論QQ:206931384

喜歡的小夥伴們在我git上給個star感激不盡、目前正在持續更新中喜歡關注的和建議小夥伴可以fork一下。你的贊美是我努力的源泉我會加油的!

繼續閱讀