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