彈幕需求
- 彈幕在屏幕上以一定的軌道移動
- 根據(jù)彈幕長度定移動速度
- 彈幕文字支持自定義(字體、格式、顏色等)
- 彈幕支持暫停及恢復(fù)
實現(xiàn)主要要解決的問題
- 彈幕如何繪制?
- 彈幕如何控制移動速度?
- 彈幕如何檢測是否碰撞?
- 彈幕如何暫停移動及恢復(fù)移動?
注:“彈幕碰撞”指彈幕移動過程中前一條展示與后一條展示不出現(xiàn)重疊。
根據(jù)已知的幾個問題,依次提出解決方案。
實現(xiàn)彈幕
彈幕如何繪制
Android 平臺DanmakuFlameMaster庫中,通過在 View 層的一幀幀的繪制來顯示彈幕。對于每一條來說,如果要實現(xiàn)彈幕的流暢性,需要保證每秒繪制的幀數(shù)處在一個較高的數(shù)字。如果出現(xiàn)大量的彈幕,同時繪制,對設(shè)備的性能要求也會很高。所以自定義繪制幀的方式,不一定適用于所有的設(shè)備。
除了自定義繪制幀的方式展示彈幕,還可以通過采用系統(tǒng)動畫的方式來實現(xiàn)彈幕文本的移動。例如 UIView 動畫、Facebook/pop 動畫。以系統(tǒng) UIView 動畫為例:
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
// bulletLabel 為彈幕內(nèi)容 label
bulletLabel.frame = CGRectMake(-bulletLabel.width, bulletLabel.y, bulletLabe.width, bulletLabel.height);
} completion:^(BOOL finished) {
//
}];
這樣實現(xiàn)了一個彈幕移動的過程。借助于系統(tǒng) UIView 動畫,實現(xiàn)一個滾動過程中,開發(fā)者只需要關(guān)注動畫開始時間、持續(xù)時間、動畫開始執(zhí)行的狀態(tài)和動畫結(jié)束執(zhí)行的狀態(tài),這里采用的 UILabel frame。具體的滾動動畫交于系統(tǒng)來負(fù)責(zé),系統(tǒng)會根據(jù)設(shè)備的性能來調(diào)整來做對應(yīng)的調(diào)整。
彈幕如何控制移動速度
首先根據(jù)文本的內(nèi)容的屬性(字體、大?。┯嬎愠鑫谋撅@示后的 Width,line 為 1,根據(jù) textWidth 及 screenWidth 來卻確定最終的 rate。
計算文本 width 可通過 boundingRect(with:options:attributes:context:)
彈幕如何檢測是否碰撞
檢測難點在于,怎么在彈幕滾動過程中檢測是否碰撞。彈幕的滾動過程中位于同一個 Y 的彈幕,新創(chuàng)建的彈幕顯示 frame 需要與正在滾動中的彈幕 frame 進行對照。如果新創(chuàng)建的彈幕滾動的過程中,不與正在滾動中的彈幕有視圖重疊。則新創(chuàng)建的彈幕以當(dāng)前 frame 作為初始位置開始滾動。
那么兩者如何進行對照呢?
默認(rèn)彈幕由右向左滾動,過程中,正在滾動中的彈幕位于左側(cè),新建的彈幕位于右側(cè)。
如果正在滾動中的彈幕(記為 danmakuL)的開始時刻及結(jié)束時刻,彈幕視圖都沒有與新建彈幕(記為 danmakuR)視圖重合。那么新建彈幕在滾動過程中也不會與正在滾動中的彈幕有視圖重合的情況出現(xiàn)。
remainTime 代表著字幕存活時間,由于字幕的存活時間是由字幕的長度解決的。字幕越長,滾動過程中速度即越快。所以過程中已 danmakuL 與 danmakuR 間最小滾動時間(miniRemainTime)為標(biāo)準(zhǔn),進行對比。如果在 miniRemainTime 內(nèi),danmakuL 與 danmakuR 之間沒有出現(xiàn)重合,那么整個過程中,彈幕也不會出現(xiàn)重合的情況。這樣就解決了彈幕碰撞的檢測。
演示代碼如下:
- (BOOL)checkIsWillHitWithWidth:(float)width danmakuL:(UILabel *)danmakuL danmakuR:(UILabel *)danmakuR {
if (danmakuL.remainTime<=0) {
return NO;
}
if (danmakuL.x + danmakuL.size.width > danmakuR.x) {
return YES;
}
float minRemainTime = MIN(danmakuL.remainTime, danmakuR.remainTime);
float xLeft = [danmakuL xWithScreenWidth:width remainTime:(danmakuL.remainTime - minRemainTime)];
float xRight = [danmakuR xWithScreenWidth:width remainTime:(danmakuR.remainTime - minRemainTime)];
if (xLeft + danmakuL.size.width > xRight) {
return YES;
}
return NO;
}
彈幕如何暫停滾動及恢復(fù)滾動
在當(dāng)前的實現(xiàn)過程中,彈幕滾動采用了 UIView 系統(tǒng)動畫。動畫暫停的基本原理是通過 View 的 presentationLayer 獲取 danmake 當(dāng)前 frame 并作為彈幕暫停后的 frame,再移除 danmake 的 layer 動畫。同時記錄已滾動時間及當(dāng)前 danmake frame。
// 暫停彈幕滾動
- (void)pauseDanmaku {
CALayer *layer = danmaku.layer;
CGRect rect = danmaku.frame;
danmaku.label.frame = rect;
[danmaku.label.layer removeAllAnimations];
}
// 開始滾動的方法與新建彈幕滾動方法相同,通過 UIView animation