彈幕,並非一個多麼複雜的功能。dom
精通數學公式V=S/t (V表明速度,S表明路程,t表明時間)(*^__^*) 測試
建立一個View,命名爲BarrageView,以及存儲彈幕數據的對象BarrageModel動畫
如下爲BarrageModel.h的內容,存儲彈幕的頭像,暱稱,和消息內容ui
@interface BarrageModel : NSObject /** 用戶暱稱 */ @property(nonatomic,copy)NSString *userName; /** 消息內容 */ @property(nonatomic,copy)NSString *userMsg; /** 用戶頭像 */ @property(nonatomic,copy)NSString *userHeadImageUrl; @end
接下來對BarrageView內容進行編輯,註釋已經儘量的詳細,所以很少作介紹atom
在.h文件中spa
#import <UIKit/UIKit.h> @class BarrageModel; @interface BarrageView : UIView /** * 記錄當前最後一個彈幕View,經過這個View來計算是顯示在哪一個彈幕軌道上 */ @property(nonatomic,retain) UIView *lastAnimateView; /** * 發送彈幕 * * @param msgModel 彈幕數據Model */ -(void)barrageSendMsg:(BarrageModel *)msgModel; @end
在.m文件中線程
#import "BarrageView.h" #import "BarrageModel.h" //屏幕的尺寸 #define SCREEN_FRAME [[UIScreen mainScreen] bounds] //屏幕的高度 #define SCREEN_HEIGHT CGRectGetHeight(SCREEN_FRAME) //屏幕的寬度 #define SCREEN_WIDTH CGRectGetWidth(SCREEN_FRAME) @interface BarrageView() { CGFloat _minSpaceTime; /** 最小間距時間 */ } /** 數據源 */ @property (nonatomic,retain)NSMutableArray *dataArr; /** 彈幕UI的重用池 */ @property (nonatomic,retain)NSMutableArray *resuingArr; @end @implementation BarrageView - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self setInterface]; } return self; } -(void)setInterface { //初始化彈幕數據源,以及重用池 self.dataArr = [NSMutableArray array]; self.resuingArr = [NSMutableArray array]; //建立第一個彈幕加入重用池做爲備用 UIView *view = [self createUI]; [self.resuingArr addObject:view]; //設置彈幕數據的初始輪詢時間 _minSpaceTime = 1; //檢查是否能夠取彈幕數據進行動畫 [self checkStartAnimatiom]; } -(void)checkStartAnimatiom { //當有數據信息的時候 if (self.dataArr.count>0) { if (self.resuingArr.count>0) { //當重用池裏面有備用的彈幕UI時 //在重用池中,取出第一個彈幕UI UIView *view = [self.resuingArr firstObject]; [self.resuingArr removeObject:view]; //取出的這個彈幕UI開始動畫 [self startAnimationWithView:view]; }else{ //當重用池沒有備用的彈幕UI時 //從新建立一個彈幕UI UIView *view = [self createUI]; //拿着這個彈幕UI開始動畫 [self startAnimationWithView:view]; } } //延遲執行,在主線程中不能調用sleep()進行延遲執行 //調用自身方法,構成一個無限循環,不停的輪詢檢查是否有彈幕數據 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_minSpaceTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self checkStartAnimatiom]; }); //於2017年12月29日更新,以上代碼會致使內存泄漏,修改以下 __weak typeof(self) weakSelf = self; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_minSpaceTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ __strong typeof(self) strongSelf = weakSelf; [strongSelf checkStartAnimatiom]; });
} -(void)startAnimationWithView:(UIView *)view { //取出第一條數據 BarrageModel *barrageModel = [self.dataArr firstObject]; //計算暱稱的長度 CGSize nameSize = [barrageModel.userName boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, 14) options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading attributes:@{ NSFontAttributeName:[UIFont systemFontOfSize:14] } context:nil].size; //計算消息的長度 CGSize msgSize = [barrageModel.userMsg boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, 14) options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading attributes:@{ NSFontAttributeName:[UIFont systemFontOfSize:14] } context:nil].size; UIImageView *headImageView; //頭像 UILabel *userNameLabel; //暱稱 UILabel *userMsgLabel; //消息內容 //進行賦值,寬度適應 for (UIView *subView in view.subviews) { if (subView.tag == 1000) { headImageView = (UIImageView *)subView; headImageView.image = [UIImage imageNamed:@""]; }else if (subView.tag == 1001){ userNameLabel = (UILabel *)subView; userNameLabel.text = barrageModel.userName; //從新設置名稱Label寬度 CGRect nameRect = userNameLabel.frame; nameRect.size.width = nameSize.width; userNameLabel.frame = nameRect; }else{ userMsgLabel = (UILabel *)subView; userMsgLabel.text = barrageModel.userMsg; //從新設置消息內容Label寬度 CGRect msgRect = userMsgLabel.frame; msgRect.size.width = msgSize.width; userMsgLabel.frame = msgRect; } } //從新設置彈幕的整體寬度 = 頭像寬度 + 頭像左右兩側距離 + (若是名字寬度大於消息內容寬度,以名字寬度爲基準,若是名字寬度小於消息內容寬度,以消息內容寬度爲基準) view.frame = CGRectMake(SCREEN_WIDTH, 0, CGRectGetWidth(headImageView.frame) + 4 + (CGRectGetWidth(userNameLabel.frame)>CGRectGetWidth(userMsgLabel.frame)?CGRectGetWidth(userNameLabel.frame):CGRectGetWidth(userMsgLabel.frame)), CGRectGetHeight(self.frame)); //無論彈幕長短,速度要求一致。 V(速度) 爲固定值 = 100(可根據實際本身調整) // S = 屏幕寬度+彈幕的寬度 V = 100(可根據實際本身調整) // V(速度) = S(路程)/t(時間) -------> t(時間) = S(路程)/V(速度); CGFloat duration = (view.frame.size.width+SCREEN_WIDTH)/100; //最小間距運行時間爲:彈幕從屏幕外徹底移入屏幕內的時間 + 間距的時間 _minSpaceTime = (view.frame.size.width + 30)/100; //最後作動畫的view _lastAnimateView = view; //彈幕UI開始動畫 [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveLinear animations:^{ //運行至左側屏幕外 CGRect frame = view.frame; view.frame = CGRectMake(-frame.size.width, 0, frame.size.width, frame.size.height); } completion:^(BOOL finished) { //動畫結束從新回到右側初始位置 view.frame = CGRectMake(SCREEN_WIDTH, 0, 0, CGRectGetHeight(self.frame)); //從新加入重用池 [self.resuingArr addObject:view]; }]; //將這個彈幕數據移除 [self.dataArr removeObject:barrageModel]; } #pragma mark public method -(void)barrageSendMsg:(BarrageModel *)msgModel{ //添加彈幕數據 [self.dataArr addObject:msgModel]; } #pragma mark 建立控件 -(UIView *)createUI { UIView *view = [[UIView alloc] initWithFrame:CGRectMake(SCREEN_WIDTH, 0, 0, CGRectGetHeight(self.frame))]; view.backgroundColor = [UIColor colorWithWhite:0 alpha:0.3]; UIImageView *headImageView = [[UIImageView alloc] initWithFrame:CGRectMake(2, 2, CGRectGetHeight(self.frame)-4, CGRectGetHeight(self.frame)-4)]; headImageView.layer.cornerRadius = headImageView.frame.size.width/2; headImageView.layer.masksToBounds = YES; headImageView.tag = 1000; headImageView.backgroundColor = [UIColor redColor]; [view addSubview:headImageView]; UILabel *userNameLabel = [[UILabel alloc] initWithFrame:CGRectMake(CGRectGetMaxX(headImageView.frame) + 2, 0, 0,14)]; userNameLabel.font = [UIFont systemFontOfSize:14]; userNameLabel.tag = 1001; [view addSubview:userNameLabel]; UILabel *userMsgLabel = [[UILabel alloc] initWithFrame:CGRectMake(CGRectGetMaxX(headImageView.frame)+2, CGRectGetMaxY(userNameLabel.frame), 0, 14)]; userMsgLabel.font = [UIFont systemFontOfSize:14]; userMsgLabel.tag = 1002; [view addSubview:userMsgLabel]; [self addSubview:view]; return view; }
最後在vc裏面code
#import "ViewController.h" #import "BarrageView.h" #import "BarrageModel.h" //屏幕的尺寸 #define SCREEN_FRAME [[UIScreen mainScreen] bounds] //屏幕的高度 #define SCREEN_HEIGHT CGRectGetHeight(SCREEN_FRAME) //屏幕的寬度 #define SCREEN_WIDTH CGRectGetWidth(SCREEN_FRAME) @interface ViewController () /** 第一個彈幕軌道 */ @property (nonatomic,retain)BarrageView *barrageViewOne; /** 第二個彈幕軌道 */ @property (nonatomic,retain)BarrageView *barrageViewTwo; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //建立第一個彈幕軌道 _barrageViewOne = [[BarrageView alloc]initWithFrame:CGRectMake(0,200, SCREEN_WIDTH, 34)]; [self.view addSubview:_barrageViewOne]; //建立第二個彈幕軌道 _barrageViewTwo = [[BarrageView alloc]initWithFrame:CGRectMake(0,300, SCREEN_WIDTH, 34)]; [self.view addSubview:_barrageViewTwo]; } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(sendMessage) userInfo:nil repeats:YES]; [timer fire]; } -(void)sendMessage { BarrageModel *model = [[BarrageModel alloc]init]; model.userName = @[@"張三",@"李四",@"王五",@"趙六",@"七七",@"八八",@"九九",@"十十",@"十一",@"十二",@"十三",@"十四"][arc4random()%12]; model.userMsg = @[@"阿達我的",@"都是vsqe12qwe",@"勝多負少的凡人歌",@"委屈翁二羣二",@"12312",@"熱帖柔荑花",@"發彼此彼此",@"OK潑墨",@"人體有圖圖",@"額外熱無若無",@"微軟將圍"][arc4random()%11]; //計算當前作動畫的彈幕UI的位置 CGFloat onePositon = _barrageViewOne.lastAnimateView.layer.presentationLayer.frame.size.width + _barrageViewOne.lastAnimateView.layer.presentationLayer.frame.origin.x; //計算當前作動畫的彈幕UI的位置 CGFloat twoPositon = _barrageViewTwo.lastAnimateView.layer.presentationLayer.frame.size.width + _barrageViewTwo.lastAnimateView.layer.presentationLayer.frame.origin.x; if ( onePositon < twoPositon ) { [_barrageViewOne barrageSendMsg:model]; }else{ [_barrageViewTwo barrageSendMsg:model]; } } @end
經一個小時的定時器測試,內存沒有增長。對象