效果
可左右拖動和通過縮放來實現不同刻度模式的切換,下面代碼只使用了兩種刻度模式.
綠色部分代表有錄制的視頻數據,該效果可根據需求自行修改.
安卓版
寫法思路完全相同
思路
繪圖主要繪制刻度線和時間數值,其中要記錄和使用的主要數值為時間戳,相應時間戳在需要繪制的界面里對應的x的值,時間參考為時間軸中線,而刻度線參考為0刻度即左邊界.需要處理的主要關系為時間戳和中間刻度所代表的時間刻度改變時各個時間戳所代表的相對于控件本身的x的值.
那么所有數據都可以通過中間線位置所代表的時間,控件本身的寬度,刻度線間距寬度和其所代表的時間長度進行換算和相互轉化來解決,而繪圖為保證性能可以通過計算只去繪制需要繪制的內容.
最終需要達到的效果是通過改變中央刻度線所代表的時間戳的值來實現整個時間軸的繪制.而我們改變時間軸就只要改變中間刻度線所代表的時間戳,然后重新繪制就可以實現各種效果,比如拖動,就是根據手指拖動的距離換算成時間長度對中央刻度線時間戳進行加減,然后不斷繪制達到拖動時整個時間軸滑動的效果.
代碼
ZFTimeLine.h
#import <UIKit/UIKit.h>
typedef enum{
ScaleTypeBig, //大
ScaleTypeSmall //小
}ScaleType; //時間軸模式
@class ZFTimeLine;
@protocol ZFTimeLineDelegate <NSObject>
- (void)timeLine:(ZFTimeLine *)timeLine moveToDate:(NSString *)date;
@end
@interface ZFTimeLine : UIView
@property (nonatomic, assign) id<ZFTimeLineDelegate> delegate;
//刷新,但不改變時間
-(void)refresh;
#pragma mark --- 刷新到到當前時間
- (void)refreshNow;
#pragma mark --- 移動到某時間
// date數據格式舉例 20170815121020
- (void)moveToDate:(NSString *)date;
#pragma mark --- 獲取時間軸指向的時間
//返回數據舉例 20170815121020
-(NSString *)currentTimeStr;
//鎖定 不可拖動
- (void)lockMove;
//解鎖 可拖動
- (void)unLockMove;
@end
ZFTimeLine.m
#import "ZFTimeLine.h"
@interface ZFTimeLine(){
float intervalValue; //小刻度寬度 默認10
NSDateFormatter *formatterScale; //時間格式化 用于獲取時刻表文字
NSDateFormatter *formatterProject; //時間格式化 用于項目同于時間格式轉化
ScaleType scaleType; //時間軸模式
NSTimeInterval currentInterval; //中間時刻對應的時間戳
CGPoint moveStart; //移動的開始點
float scaleValue; //縮放時記錄開始的間距
BOOL onTouch; //是否在觸摸狀態(tài)
}
@end
@implementation ZFTimeLine
- (instancetype)initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
if (self) {
self.backgroundColor = [UIColor blackColor];
self.alpha = 0.8;
intervalValue = 10;
formatterScale = [[NSDateFormatter alloc]init];
[formatterScale setDateFormat:@"HH:mm"];
formatterProject = [[NSDateFormatter alloc]init];
[formatterProject setDateFormat:@"yyyyMMddHHmmss"];
scaleType = ScaleTypeBig;
[self timeNow];
self.multipleTouchEnabled = YES;
onTouch = NO;
}
return self;
}
-(void)layoutSubviews{
[self setNeedsDisplay];
}
#pragma mark --- 觸摸事件
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
if (!self.userInteractionEnabled) {
return;
}
onTouch = YES;
if (touches.count == 1) {
UITouch *touch = [touches anyObject];
moveStart = [touch locationInView:self];
}else if (touches.count == 2){
NSArray *arr = [touches allObjects];
UITouch *touch1 = arr[0];
UITouch *touch2 = arr[1];
scaleValue = fabs([touch2 locationInView:self].x - [touch1 locationInView:self].x);
}
}
-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
if (!self.userInteractionEnabled) {
return;
}
if (touches.count == 1) {
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
float x = point.x - moveStart.x;
currentInterval = currentInterval - [self secondsOfIntervalValue] * x;
moveStart = point;
[self setNeedsDisplay];
}else if (touches.count == 2){
NSArray *arr = [touches allObjects];
UITouch *touch1 = arr[0];
UITouch *touch2 = arr[1];
float value = fabs([touch2 locationInView:self].x - [touch1 locationInView:self].x) ;
if (scaleType == ScaleTypeBig) {
if (scaleValue - value < 0) {//變大
intervalValue = intervalValue + (value - scaleValue)/100;
if (intervalValue >= 15) {
scaleType = ScaleTypeSmall;
intervalValue = 10;
}
}else{//縮小
intervalValue = intervalValue + (value - scaleValue)/100;
if (intervalValue < 10) {
intervalValue = 10;
}
}
}else{
if (scaleValue - value < 0) {//變大
intervalValue = intervalValue + (value - scaleValue)/100;
if (intervalValue >= 15) {
intervalValue = 15;
}
}else{//縮小
intervalValue = intervalValue + (value - scaleValue)/100;
if (intervalValue < 10) {
scaleType = ScaleTypeBig;
intervalValue = 10;
}
}
}
[self setNeedsDisplay];
}
}
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
if (!self.userInteractionEnabled) {
return;
}
onTouch = NO;
if (self.delegate && [self.delegate respondsToSelector:@selector(timeLine:moveToDate:)]) {
[self.delegate timeLine:self moveToDate:[self currentTimeStr]];
}
[DFTime delaySec:0.5 perform:^{
}];
}
//鎖定 不可拖動
- (void)lockMove{
self.userInteractionEnabled = NO;
}
//解鎖 可拖動
- (void)unLockMove{
self.userInteractionEnabled = YES;
}
//刷新,但不改變時間
-(void)refresh{
[self setNeedsDisplay];
}
#pragma mark --- 刷新到當到前時間
- (void)refreshNow{
if (onTouch || !self.userInteractionEnabled) {
return;
}
[self timeNow];
[self setNeedsDisplay];
}
#pragma mark --- 移動到某時間
// date數據格式舉例 20170815121020
- (void)moveToDate:(NSString *)date{
if (onTouch || !self.userInteractionEnabled) {
return;
}
currentInterval = [self intervalWithTime:date];
[self setNeedsDisplay];
// if (self.delegate && [self.delegate respondsToSelector:@selector(timeLine:moveToDate:)]) {
// [self.delegate timeLine:self moveToDate:[self currentTimeStr]];
// }
}
#pragma mark --- 獲取時間軸指向的時間
-(NSString *)currentTimeStr{
return [self projectTimeWithInterval:currentInterval];
}
//設置中心刻度為當前時間
- (void)timeNow{
currentInterval = [[NSDate date] timeIntervalSince1970];
}
//寬度1所代表的秒數
- (float)secondsOfIntervalValue{
if (scaleType == ScaleTypeBig) {
return 6.0*60.0/intervalValue;
}else if (scaleType == ScaleTypeSmall){
return 60.0/intervalValue;
}
return 6.0*60.0/intervalValue;
}
//繪圖
-(void)drawRect:(CGRect)rect{
//計算x=0時對應的時間戳
float centerX = rect.size.width/2.0;
NSTimeInterval leftInterval = currentInterval - centerX * [self secondsOfIntervalValue];
NSTimeInterval rightInterval = currentInterval + centerX * [self secondsOfIntervalValue];
//左邊第一個刻度對應的x值和時間戳
float x;
NSTimeInterval interval;
if (scaleType == ScaleTypeBig) {
float a = leftInterval/(60.0*6.0);
interval = (((int)a) + 1) * (60.0 * 6.0);
x = (interval - leftInterval) / [self secondsOfIntervalValue];
}else {
float a = leftInterval/(60.0);
interval = (((int)a) + 1) * (60.0);
x = (interval - leftInterval) / [self secondsOfIntervalValue];
}
CGContextRef contex = UIGraphicsGetCurrentContext();
//視頻文件信息
NSArray * array = ......;
if (array == nil) {
array = @[];
}
for (VideoInfo *info in array) {
NSTimeInterval start = [self intervalWithTime:[NSString stringWithFormat:@"%lld",info.date]];
NSTimeInterval end = start + info.time;
if ((start > leftInterval && start < rightInterval) || (end > leftInterval && end < rightInterval ) || (start < leftInterval && end > rightInterval)) {
//計算起始位置對應的x值
float startX = (start - leftInterval)/[self secondsOfIntervalValue];
//計算時間長度對應的寬度
float length = (info.time)/[self secondsOfIntervalValue] + 0.5;
if ([info.path containsString:@"SOS"]) {
[self drawRedRect:startX Context:contex length:length];
}else{
[self drawGreenRect:startX Context:contex length:length];
}
}
}
while (x >= 0 && x <= rect.size.width) {
int b;
if (scaleType == ScaleTypeBig) {
b = 60 * 6;
}else{
b = 60;
}
int rem = ((int)interval) % (b * 5);
if (rem != 0) {//小刻度
[self drawSmallScale:x context:contex height:rect.size.height];
}else{//大刻度
[self drawBigScale:x context:contex height:rect.size.height];
[self drawText:x interval:interval context:contex height:rect.size.height];
}
x = x + intervalValue;
interval = interval + b;
}
[self drawCenterLine:rect.size.width/2 context:contex height:rect.size.height];
}
#pragma mark --- 畫小刻度
-(void)drawSmallScale:(float)x context:(CGContextRef)ctx height:(float)height{
// 創(chuàng)建一個新的空圖形路徑霉赡。
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, x-0.5, height-5);
CGContextAddLineToPoint(ctx, x-0.5, height);
// 設置圖形的線寬
CGContextSetLineWidth(ctx, 1.0);
// 設置圖形描邊顏色
CGContextSetStrokeColorWithColor(ctx, [UIColor whiteColor].CGColor);
// 根據當前路徑冠跷,寬度及顏色繪制線
CGContextStrokePath(ctx);
}
#pragma mark --- 畫大刻度
-(void)drawBigScale:(float)x context:(CGContextRef)ctx height:(float)height{
// 創(chuàng)建一個新的空圖形路徑。
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, x-0.5, height-10);
CGContextAddLineToPoint(ctx, x-0.5, height);
// 設置圖形的線寬
CGContextSetLineWidth(ctx, 1.0);
// 設置圖形描邊顏色
CGContextSetStrokeColorWithColor(ctx, [UIColor whiteColor].CGColor);
// 根據當前路徑趴拧,寬度及顏色繪制線
CGContextStrokePath(ctx);
}
#pragma mark --- 畫中間線
-(void)drawCenterLine:(float)x context:(CGContextRef)ctx height:(float)height{
// 創(chuàng)建一個新的空圖形路徑哥倔。
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, x-0.5, 0);
CGContextAddLineToPoint(ctx, x-0.5, height);
// 設置圖形的線寬
CGContextSetLineWidth(ctx, 1.0);
// 設置圖形描邊顏色
CGContextSetStrokeColorWithColor(ctx, [UIColor whiteColor].CGColor);
// 根據當前路徑,寬度及顏色繪制線
CGContextStrokePath(ctx);
}
#pragma mark --> 在刻度上標記文本
-(void)drawText:(float)x interval:(NSTimeInterval)interval context:(CGContextRef)ctx height:(float)height{
NSString *text = [self timeWithInterval:interval];
CGContextSetRGBFillColor(ctx, 1, 0, 0, 1);
UIFont *font = [UIFont systemFontOfSize:10];
NSMutableParagraphStyle *paragraph=[[NSMutableParagraphStyle alloc]init];
paragraph.alignment=NSTextAlignmentCenter;//居中
[text drawInRect:CGRectMake(x-15, height-21, 30, 10) withAttributes:@{NSFontAttributeName : font,NSForegroundColorAttributeName:[UIColor whiteColor],NSParagraphStyleAttributeName:paragraph}];
}
#pragma mark --- 時間戳轉 顯示的時刻文字
-(NSString *)timeWithInterval:(NSTimeInterval)interval{
NSDate *date = [NSDate dateWithTimeIntervalSince1970:interval];
return [formatterScale stringFromDate:date];
}
#pragma mark --- 文字轉時間戳
-(NSTimeInterval)intervalWithTime:(NSString *)time{
NSDate *date = [formatterProject dateFromString:time];
return [date timeIntervalSince1970];
}
#pragma mark --- 時間戳轉 當前的時間 格式舉例: 20170814122034
-(NSString *)projectTimeWithInterval:(NSTimeInterval)interval{
NSDate *date = [NSDate dateWithTimeIntervalSince1970:interval];
return [formatterProject stringFromDate:date];
}
#pragma mark --- 綠色色塊
-(void)drawGreenRect:(float)x Context:(CGContextRef)ctx length:(float)length{
// 創(chuàng)建一個新的空圖形路徑。
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, x, 0.0);
CGContextAddLineToPoint(ctx, x+length, 0.0);
CGContextAddLineToPoint(ctx, x+length, 25.0);
CGContextAddLineToPoint(ctx, x, 25.0);
// 關閉并終止當前路徑的子路徑,并在當前點和子路徑的起點之間追加一條線
CGContextClosePath(ctx);
// 設置當前視圖填充色(淺灰色)
CGContextSetFillColorWithColor(ctx, [UIColor colorWithRed:0/255.0
green:139.0/255.0
blue:52.0/255.0
alpha:0.90].CGColor);
// 繪制當前路徑區(qū)域
CGContextFillPath(ctx);
}
#pragma mark --- 綠色色塊
-(void)drawRedRect:(float)x Context:(CGContextRef)ctx length:(float)length{
// 創(chuàng)建一個新的空圖形路徑之众。
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, x, 0.0);
CGContextAddLineToPoint(ctx, x+length, 0.0);
CGContextAddLineToPoint(ctx, x+length, 25.0);
CGContextAddLineToPoint(ctx, x, 25.0);
// 關閉并終止當前路徑的子路徑,并在當前點和子路徑的起點之間追加一條線
CGContextClosePath(ctx);
// 設置當前視圖填充色(淺灰色)
CGContextSetFillColorWithColor(ctx, [UIColor colorWithRed:233.0/255.0
green:64.0/255.0
blue:73.0/255.0
alpha:1.0].CGColor);
// 繪制當前路徑區(qū)域
CGContextFillPath(ctx);
}
@end
使用
使用時直接在storyboard或者xib中拖一個view設置好約束,然后將它設置為ZFTimeLine即可.
因為項目用到,而且這種效果的資料不太好找,所以在解決之后記錄和分享一下.
也方便我以后處理繪圖需求時用作參考.