IOS 自定義彈幕實現(xiàn)

寫在開篇

最近做了個視頻直播項目,當(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ā)表你的想法,最后希望這篇文章能真正的幫助到大家。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容