iOS彈幕demo網上有很多,而且大多數是開源的,很多源代碼我們可以自己查看。
彈幕有哪些特點呢?
最基本的特點:
1、文字越長的彈幕,跑的越快,彈幕的速度和文字長度有關系。
2、彈幕不相互碰撞。
3、如果有數據,會一條接著一跳的播放。
基於以上的基本特點,我們可以很簡單的想到iOS中可以實現的方式:
1、在view中一貞一貞的draw
2、用系統的動畫去做
上面一種柔性很強,可是對編程功底要求比較高,搞不好又影響了性能,還做不出好的效果。
下面一種很簡單,只需要動畫做好分段,處理好動畫間的銜接就可以了;可是柔性相對較差一點。
這裡采用系統動畫來做,這樣我們只需要做好下面幾個方面就OK了:
1、重用或者釋放;
2、暫停和繼續;
3、load新數據;
4、碰撞問題;
這裡簡單的貼一下思路,希望能為各位敞開一扇門。
首先我們做一個單獨的帶動畫的視圖,作為我們的彈幕基本試圖,後面我們只需要把這些試圖組合起來就成為我們想要的彈幕了。
@interface BulletView : UIView @property (nonatomic, copy) void(^moveBlock)(CommentMoveStatus status);//狀態回調的block @property (nonatomic, assign) NSInteger trajectory;//所在軌道編號 - (instancetype)initWithCommentDic:(BulletSettingDic *)commentDic;//這裡的BulletSettingDic是一個自定義的類,裡面主要設置了我們彈幕的背景顏色、文字顏色、字體、速率等 - (void)reloadDataWithDic:(BulletSettingDic *)reloadDic;//寫這個方法方便重用 - (void)startAnimation; - (void)stopAnimation; - (void)pauseAnimation; - (void)resumeAnimation; @end
CommentMoveStatus是一個枚舉集合,裡面設置了動畫的幾種狀態:
typedef NS_ENUM(NSInteger, CommentMoveStatus) { MoveIn, Enter, MoveOut };
這個裡面的內容,我們可以隨便自定義,然後修改寫入視圖就好了。
@interface BulletSettingDic : NSObject { NSMutableDictionary *_settingDic; } //設置字顏色 -(void)setBulletTextColor:(UIColor *)color; -(UIColor *)bulletTextColor; //設置背景顏色 -(void)setBulletBackgroundColor:(UIColor *)color; -(UIColor *)bulletBackgroundColor; //設置字體 -(void)setBulletTextFont:(UIFont *)font; -(UIFont *)bulletTextFont; //設置內容 -(void)setbulletText:(NSString *)text; -(NSString *)bulletText; //設置高度 -(void)setBulletHeight:(CGFloat)height; -(CGFloat)bulletHeight; //設置動畫時長 -(void)setBulletAnimationDuration:(float)duration; -(float)bulletAnimationDuration; //設置速度比率 -(void)setBulletAnimationSpeedRate:(float)speedRate; -(float)bulletAnimationSpeedRate; -(NSMutableDictionary *)settingDic;@end
下面是核心思路:
//開始動畫 - (void)startAnimation { __block CGRect frame = self.frame; __unsafe_unretained typeof(self)weakSelf = self; //計算移動的時間 CGFloat dur = (CGRectGetMinX(frame)-_screenWidth)/_real_speed; [UIView animateWithDuration:dur delay:0 options:UIViewAnimationOptionCurveLinear animations:^{ frame.origin.x = _screenWidth; weakSelf.frame = frame; } completion:^(BOOL finished) { [weakSelf.layer removeAllAnimations]; //彈幕開始進入屏幕 if (weakSelf.moveBlock) weakSelf.moveBlock(MoveIn); [weakSelf beginMoveIn]; }]; } //開始移入-->完全進入 -(void)beginMoveIn { __block CGRect frame = self.frame; __unsafe_unretained typeof(self)weakSelf = self; //計算移動的時間 CGFloat dur = CGRectGetWidth(frame)/_real_speed; [UIView animateWithDuration:dur delay:0 options:UIViewAnimationOptionCurveLinear animations:^{ frame.origin.x = _screenWidth-CGRectGetWidth(frame); weakSelf.frame = frame; } completion:^(BOOL finished) { [weakSelf.layer removeAllAnimations]; //彈幕完全進入屏幕 if (weakSelf.moveBlock) weakSelf.moveBlock(Enter); [weakSelf enterIn]; }]; } //完全進入-->完全移出 -(void)enterIn { __block CGRect frame = self.frame; __unsafe_unretained typeof(self)weakSelf = self; CGFloat dur = _screenWidth/_real_speed; [UIView animateWithDuration:dur delay:0 options:UIViewAnimationOptionCurveLinear animations:^{ frame.origin.x = -CGRectGetWidth(frame); weakSelf.frame = frame; } completion:^(BOOL finished) { //彈幕完全離開屏幕 if (weakSelf.moveBlock) weakSelf.moveBlock(MoveOut); [weakSelf.layer removeAllAnimations]; [weakSelf removeFromSuperview]; }]; }這裡面,我做了三段動畫:開始-->移入,移入-->完全移入,完全移入-->完全移出,然後每一個完成後都會有相應的block回調出去(這樣做是為了方便我們根據狀態進行數據和視圖的處理,下面會用到)。
_real_speed是動畫移動的實際速度,它是由動畫移動的速度率和速度相乘得到的,動畫的速度是通過屏幕寬度和文本寬度以及動畫時間計算得到的,速度率是設置的。
關於暫停和繼續,我使用CALayer裡面與CAMediaTimming相關的方法,改變速度,紀錄時間,做到的。代碼如下:
//暫停動畫 - (void)pauseAnimation { CALayer *layer = self.layer; layer.fillMode = kCAFillModeForwards; CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil]; layer.speed = 0.0; layer.timeOffset = pausedTime; } //繼續動畫 - (void)resumeAnimation { CALayer*layer = self.layer; CFTimeInterval pausedTime = [layer timeOffset]; layer.speed = 1.0; layer.timeOffset = 0.0; layer.beginTime = 0.0; CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime; layer.beginTime = timeSincePause; }停止和開始就不詳述了。
下面說視圖的重用,我用了兩個數組來做視圖的重用,當有彈幕視圖移出屏幕的時候我會把移出的視圖保存在一個數組中。
在下一個視圖制作的時候,直接設置取出來重新加載數據,而不是重新創建對象。
核心操作很簡單:
if (_reuseableBulletArray.count) { view = [_reuseableBulletArray firstObject]; [view reloadDataWithDic:_bulletDic]; [_reuseableBulletArray removeObjectAtIndex:0]; }else { view = [[BulletView alloc] initWithCommentDic:_bulletDic]; }[_bulletArray addObject:view];
然後在合適的地方(一般是有彈幕視圖移出屏幕的地方):
[_bulletArray removeObject:weakBulletView]; [_reuseableBulletArray addObject:weakBulletView];
這樣就可以不用創建很多的對象了。