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];
這樣就可以不用創建很多的對象了。