代碼詳解iOS視頻直播彈幕功能
本篇內(nèi)容通過步驟詳細(xì)給大家講解了iOS視頻直播彈幕的原理以及實現(xiàn)代碼分析,以下就是全部內(nèi)容:
1.彈幕的實現(xiàn)性分析
首先,從視覺上明確當(dāng)前彈幕所具有的功能
從屏幕右側(cè)滑入左側(cè),直至完全消失
不管是長的彈幕,還是短的彈幕,速度一致(可能有的需求是依據(jù)彈幕長度,調(diào)整速度)
有彈幕軌道,不是隨機產(chǎn)生的彈幕
彈幕不會進行重疊
接下來從功能角度思考需要做什么
重用機制,類似tableView有一個重用池,每個彈幕就是一個cell,當(dāng)有彈幕發(fā)送的時候,如果當(dāng)前的重用池沒有控件,則創(chuàng)建一個新的控件,如果重用池里面有控件,則拿出這個控件,開始做動畫,在動畫結(jié)束后重新將該控件重歸重用池。
速度要求一致的話,需要考慮幾點,首先如下圖所示,紅色代表彈幕起始位置,藍(lán)色代表彈幕終止位置,長度代表它們的實際長度。當(dāng)我們設(shè)定動畫的時候,采用[UIView animationWithDuration.....]這個動畫,設(shè)定duration為3s的話那么彈幕1的速度為(屏幕寬度+彈幕1寬度)/3,彈幕2的速度為(屏幕寬度+彈幕2寬度)/3,因為彈幕2長度大于彈幕1的長度,所以彈幕2的速度大于彈幕1的速度。(對于依據(jù)彈幕長度調(diào)整速度的需求來說,這里相對簡單一些,不需要專門去計算速度,唯一麻煩的是需要考慮速度不一致帶來的重疊問題)
2.開始準(zhǔn)備
精通數(shù)學(xué)公式V=S/t (V代表速度,S代表路程,t代表時間)(*^__^*)
3.正式開始
創(chuàng)建一個View,命名為BarrageView,以及存儲彈幕數(shù)據(jù)的對象BarrageModel
以下為BarrageModel.h的內(nèi)容,存儲彈幕的頭像,昵稱,和消息內(nèi)容
@interface BarrageModel : NSObject /** 用戶昵稱 */ @property(nonatomic,copy)NSString *userName; /** 消息內(nèi)容 */ @property(nonatomic,copy)NSString *userMsg; /** 用戶頭像 */ @property(nonatomic,copy)NSString *userHeadImageUrl; @end
接下來對BarrageView內(nèi)容進行編輯,注釋已經(jīng)盡可能的詳細(xì),因此不多做介紹
在.h文件中
#import <UIKit/UIKit.h> @class BarrageModel; @interface BarrageView : UIView /** * 記錄當(dāng)前最后一個彈幕View,通過這個View來計算是顯示在哪個彈幕軌道上 */ @property(nonatomic,retain) UIView *lastAnimateView; /** * 發(fā)送彈幕 * * @param msgModel 彈幕數(shù)據(jù)Model */ -(void)barrageSendMsg:(BarrageModel *)msgModel; @end
在.m文件中
#import <UIKit/UIKit.h>
@class BarrageModel;
@interface BarrageView : UIView
/**
* 記錄當(dāng)前最后一個彈幕View,通過這個View來計算是顯示在哪個彈幕軌道上
*/
@property(nonatomic,retain) UIView *lastAnimateView;
/**
* 發(fā)送彈幕
*
* @param msgModel 彈幕數(shù)據(jù)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; /** 最小間距時間 */
}
/** 數(shù)據(jù)源 */
@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
{
//初始化彈幕數(shù)據(jù)源,以及重用池
self.dataArr = [NSMutableArray array];
self.resuingArr = [NSMutableArray array];
//創(chuàng)建第一個彈幕加入重用池作為備用
UIView *view = [self createUI];
[self.resuingArr addObject:view];
//設(shè)置彈幕數(shù)據(jù)的初始輪詢時間
_minSpaceTime = 1;
//檢查是否可以取彈幕數(shù)據(jù)進行動畫
[self checkStartAnimatiom];
}
-(void)checkStartAnimatiom
{
//當(dāng)有數(shù)據(jù)信息的時候
if (self.dataArr.count>0) {
if (self.resuingArr.count>0) { //當(dāng)重用池里面有備用的彈幕UI時
//在重用池中,取出第一個彈幕UI
UIView *view = [self.resuingArr firstObject];
[self.resuingArr removeObject:view];
//取出的這個彈幕UI開始動畫
[self startAnimationWithView:view];
}else{ //當(dāng)重用池沒有備用的彈幕UI時
//重新創(chuàng)建一個彈幕UI
UIView *view = [self createUI];
//拿著這個彈幕UI開始動畫
[self startAnimationWithView:view];
}
}
//延遲執(zhí)行,在主線程中不能調(diào)用sleep()進行延遲執(zhí)行
//調(diào)用自身方法,構(gòu)成一個無限循環(huán),不停的輪詢檢查是否有彈幕數(shù)據(jù)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_minSpaceTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self checkStartAnimatiom];
});
}
-(void)startAnimationWithView:(UIView *)view
{
//取出第一條數(shù)據(jù)
BarrageModel *barrageModel = [self.dataArr firstObject];
//計算昵稱的長度
CGSize nameSize = [barrageModel.userName boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, 14) options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading attributes:@{ } 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; //消息內(nèi)容
//進行賦值,寬度適應(yīng)
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;
//重新設(shè)置名稱Label寬度
CGRect nameRect = userNameLabel.frame;
nameRect.size.width = nameSize.width;
userNameLabel.frame = nameRect;
}else{
userMsgLabel = (UILabel *)subView;
userMsgLabel.text = barrageModel.userMsg;
//重新設(shè)置消息內(nèi)容Label寬度
CGRect msgRect = userMsgLabel.frame;
msgRect.size.width = msgSize.width;
userMsgLabel.frame = msgRect;
}
}
//重新設(shè)置彈幕的總體寬度 = 頭像寬度 + 頭像左右兩側(cè)距離 + (如果名字寬度大于消息內(nèi)容寬度,以名字寬度為基準(zhǔn),如果名字寬度小于消息內(nèi)容寬度,以消息內(nèi)容寬度為基準(zhǔn))
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(可根據(jù)實際自己調(diào)整)
// S = 屏幕寬度+彈幕的寬度 V = 100(可根據(jù)實際自己調(diào)整)
// V(速度) = S(路程)/t(時間) -------> t(時間) = S(路程)/V(速度);
CGFloat duration = (view.frame.size.width+SCREEN_WIDTH)/100;
//最小間距運行時間為:彈幕從屏幕外完全移入屏幕內(nèi)的時間 + 間距的時間
_minSpaceTime = (view.frame.size.width + 30)/100;
//最后做動畫的view
_lastAnimateView = view;
//彈幕UI開始動畫
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
//運行至左側(cè)屏幕外
CGRect frame = view.frame;
view.frame = CGRectMake(-frame.size.width, 0, frame.size.width, frame.size.height);
} completion:^(BOOL finished) {
//動畫結(jié)束重新回到右側(cè)初始位置
view.frame = CGRectMake(SCREEN_WIDTH, 0, 0, CGRectGetHeight(self.frame));
//重新加入重用池
[self.resuingArr addObject:view];
}];
//將這個彈幕數(shù)據(jù)移除
[self.dataArr removeObject:barrageModel];
}
#pragma mark public method
-(void)barrageSendMsg:(BarrageModel *)msgModel{
//添加彈幕數(shù)據(jù)
[self.dataArr addObject:msgModel];
}
#pragma mark 創(chuàng)建控件
-(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里面
#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]; //創(chuàng)建第一個彈幕軌道 _barrageViewOne = [[BarrageView alloc]initWithFrame:CGRectMake(0,200, SCREEN_WIDTH, 34)]; [self.view addSubview:_barrageViewOne]; //創(chuàng)建第二個彈幕軌道 _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 = @[@"阿達(dá)個人",@"都是vsqe12qwe",@"勝多負(fù)少的凡人歌",@"委屈翁二群二",@"12312",@"熱帖柔荑花",@"發(fā)彼此彼此",@"OK潑墨",@"人體有圖圖",@"額外熱無若無",@"微軟將圍"][arc4random()%11]; //計算當(dāng)前做動畫的彈幕UI的位置 CGFloat onePositon = _barrageViewOne.lastAnimateView.layer.presentationLayer.frame.size.width + _barrageViewOne.lastAnimateView.layer.presentationLayer.frame.origin.x; //計算當(dāng)前做動畫的彈幕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
4.測試結(jié)論
經(jīng)一個小時的定時器測試,內(nèi)存沒有增加。
相關(guān)文章
iOS應(yīng)用UI開發(fā)中的字體和按鈕控件使用指南
這篇文章主要介紹了iOS應(yīng)用UI開發(fā)中的字體和按鈕控件使用指南,分別簡單講解了UILabel和UIButton的用法,需要的朋友可以參考下2016-01-01iOS自定義UICollectionViewFlowLayout實現(xiàn)圖片瀏覽效果
這篇文章主要介紹了iOS自定義UICollectionViewFlowLayout實現(xiàn)圖片瀏覽效果的相關(guān)資料,需要的朋友可以參考下2016-03-03如何通過Objective-C的枚舉學(xué)習(xí)iOS中位操作.md詳解
這篇文章主要給大家介紹了關(guān)于如何通過Objective-C的枚舉學(xué)習(xí)iOS中位操作.md的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對各位iOS開發(fā)者們具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03