寫在開篇
最近做了個視頻直播項目,當(dāng)用到彈幕時,找了很多網(wǎng)上彈幕demo。當(dāng)時因為項目進(jìn)度的原因,就隨便選了一個漂亮的集成了,也沒有去研究其中具體是如何實現(xiàn)的。如今項目完成,就利用空余的時間來研究了下。
在我尋找一個合適的demo集成的時候,就發(fā)現(xiàn)網(wǎng)上提供的demo都是將一個lable作為一條彈幕,然后控制lable做動畫,因為項目UI會對彈幕做明確的樣式規(guī)定,所以單純的一個lable根本就滿足不了項目的需求。為什么不能將一個自定義的cell作為一條彈幕,控制cell做動畫,作為使用者,則只用關(guān)心cell的樣式和在cell上展示的數(shù)據(jù)呢?于是,我就開始了這篇文章。
實現(xiàn)流程
在實現(xiàn)一個功能之前我們需要知道這個功能是做什么的,然后根據(jù)這個功能構(gòu)建一個大概的思路流程,最后將思路流程細(xì)化、修改、直至功能實現(xiàn)。
彈幕的功能我就不必多說,而我實現(xiàn)這個思路流程與具體實現(xiàn)如下
思路流程:
1.收到彈幕消息數(shù)組,立即緩存
2.定時從緩存中取出n條消息模型用來展示
3.遍歷取出的消息,為消息找一個合適的cell用來展示
4.遍歷每一條軌道(彈幕運(yùn)動的路徑),為彈幕找一條合適的軌道
5.開始cell的動畫,并清除緩存
具體實現(xiàn):
1、消息緩存
當(dāng)收到彈幕消息時就調(diào)用BarrageView對象的insertBarrages:immediatelyShow:方法將消息添加到緩存中,具體實現(xiàn)如下:
[BarrageView checkElementOfBarrages:barrages];?
? ?self.count = barrages.count;
? ?if (barrages.count) {
? ? ? [self assortDataArray:barrages];
? ?}
? ?if (flag == YES && isStart == NO) {
? ?isStart = YES;
? ?[self startAnimation];
} ?
在添加消息模型到緩存之前需要先對消息模型進(jìn)行檢測checkElementOfBarrages:因為在計算彈幕動畫時間時必須要知道彈幕的寬度,所以消息模型必須要遵守BarrageModelAble協(xié)議,即告訴BarrageVIew彈幕的寬度
2.取出模型
從緩存中取模型subArrayWithNumber:方法具體實現(xiàn)如下:
NSMutableArray *array = [NSMutableArray array];
if (self.highPrioritys.count) {
? ?[array addObjectsFromArray:self.highPrioritys];
}
if (self.mediumPrioritys.count) {
? ?[array addObjectsFromArray:self.mediumPrioritys];
}
if (self.lowPrioritys.count) {
? ?[array addObjectsFromArray:self.lowPrioritys];
}
if (array.count >= number) {
? ?NSArray *subArray = [array subarrayWithRange:NSMakeRange(0, number)];
? ?[self removeModels:subArray];
? ?return subArray;
}else {
? ?[self removeModels:array];
? ?return array;
} ??
因為BarrageModelAble協(xié)議中要求對消息模型進(jìn)行優(yōu)先級(PriorityLevel)區(qū)分,我將三個不同優(yōu)先級的數(shù)組按優(yōu)先級順序拼接成一個數(shù)組,然后從大數(shù)組中取出n個元素,并將這n個元素從緩存中刪除
3.誰來展示
BarrageViewCell *currentCell = [self.dataSouce barrageView:self cellForRowAtIndex:[self.dataArray indexOfObject:obj]];
尋找一個合適的cell來展示,根據(jù)標(biāo)識符從cell的緩存池中取,如果取不到就創(chuàng)建一個cell。
4.在哪展示
遍歷所有的軌道,有的軌道上沒有彈幕--空閑軌道,有的軌道上有彈幕--非空閑軌道。所以需要對可用的軌道進(jìn)行一個分類,具體實現(xiàn)如下:
for (int row = 0; row < numbers; row++) {
? ?if (showCells.count == 0) {
? ?[freeOrbits addObject:[NSNumber numberWithInt:row]];
? ?}else {
? ?BOOL flag = NO;//標(biāo)記row軌道上是否有cell正在執(zhí)行動畫
? ?for (int index = (int)showCells.count - 1; index >= 0; index--) {
? ?BarrageViewCell *cell = showCells[index];
? ?if (cell.row == row) {//找到row軌道上正在滾動的最后一條彈幕
? ? ? flag = YES;
? ? ? if ([self examineColide:cell] == NO) {
? ? ? ? ?[orbits addObject:[NSNumber numberWithInt:row]];
? ? ? ? ? ? }
? ? ? break;
? ? ? }
? ?}
? ?if (flag == NO) {
? ? ? [freeOrbits addObject:[NSNumber numberWithInt:row]];
? ? ? }
? ?}
} ??
空閑軌道一定可用,但非空閑軌道還需要還需要進(jìn)行碰撞檢測examineColide:才能確定其是否可用,碰撞檢測具體實現(xiàn)如下:
- (BOOL)examineColide:(BarrageViewCell *)cell{
? ?NSTimeInterval t1 = self.duration - self.cellWidth / self.speed;
? ?NSDate *nowDate = [NSDate date];
? ?if (_stopDate) {
? ? ? nowDate = _stopDate;
? ?}
? ?NSDate *date1 = [nowDate dateByAddingTimeInterval:t1];
? ?NSDate *date2 = [cell.startTime dateByAddingTimeInterval:cell.duration];
? ?if ([date1 compare:date2] == NSOrderedDescending) {
? ? ? return NO;
? ?}else {
? ? ? return YES;
? ?}
} ?
碰撞檢測主要是根據(jù)當(dāng)前軌道上最后一個正在滾動的彈幕的結(jié)束時間與當(dāng)前需要開始滾動的彈幕從現(xiàn)在開始滾動的結(jié)束時間進(jìn)行一個對比。
選出可用軌道之后,就從可用軌道中隨機(jī)出一個軌道,用于彈幕展示,當(dāng)然先從空閑軌道中隨。具體實現(xiàn)如下:
if (orbits.count == 0 && freeOrbits.count == 0) {
? ?_row = -1;
}else {
? ?if (freeOrbits.count > 0) {
? ? ? int index = arc4random_uniform((u_int32_t)freeOrbits.count);
? ? ? _row? ? ? = freeOrbits[index].integerValue;?
? ?}else {
? ? ? int index = arc4random_uniform((u_int32_t)orbits.count);
? ? ? _row? ? ? = orbits[index].integerValue;
? ?}
} ??
5.如何展示
至于彈幕的動畫就非常簡單了,即控制cell讓其從屏幕右邊滾動到屏幕左邊,具體實現(xiàn)如下:
CABasicAnimation *move? = [CABasicAnimation animation];
move.keyPath? ? ? ? ? ? = @"position";
CGRect frame? ? ? ? ? ? = self.frame;
CGPoint fromPoint? ? ? ? = CGPointMake(frame.origin.x, frame.origin.y);
CGPoint toPoint? ? ? ? ? = CGPointMake(- CGRectGetWidth(frame), frame.origin.y);
move.fromValue? ? ? ? ? = [NSValue valueWithCGPoint:fromPoint];
move.toValue? ? ? ? ? ? = [NSValue valueWithCGPoint:toPoint];
move.duration? ? ? ? ? ? = duration;
move.delegate? ? ? ? ? ? = self;
move.removedOnCompletion = YES;
[self.layer addAnimation:move forKey:nil]; ?
動畫是在異步線程中執(zhí)行,動畫添加到圖層中之后就應(yīng)該重復(fù)到第二步,從緩存中取新的消息模型用來展示,直到?jīng)]有緩存數(shù)據(jù)為止。
至此,就大功告成了,下面是實現(xiàn)的效果圖

功能如何使用可以參照demo,最后附上Demo地址。
寫在結(jié)尾
因為個人能力有限,demo中難免有些BUG,如果遇到請在留言中指出,方便我好及時修改。或者你有什么更好的意見或建議,也可以提出來,我們相互交流、相互學(xué)習(xí)。又或者你將這個功能集成在項目中時,因為項目原因需要增加其他的功能,你也可以提出,我也會依據(jù)個人能力添加新的功能,或是給你一些建議??傊憧梢栽诹粞詤^(qū)發(fā)表你的想法,最后希望這篇文章能真正的幫助到大家。