仿照著做了一個(gè)時(shí)間軸的控件,類似螢石的效果延都,先上圖
實(shí)現(xiàn)如下:
1 - 整個(gè)控件是基于UIScrollView做的
2 - 初始化scrollView的時(shí)候雷猪,設(shè)置scrollView的contentSize為scrollView的2倍寬,高不變晰房。
_scrollView.contentSize = CGSizeMake(2 * self.width, self.height);
3 - 接下來(lái)初始化contentView求摇,我的代碼中contentView是用的UIImageView(只有能實(shí)現(xiàn),用UIView啥的都行)殊者,設(shè)置contentView的frame為scrollView的contentSize与境。
_contentView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 2 * self.width, self.height)];
_contentView.userInteractionEnabled = YES;
[self.scrollView addSubview:_contentView];
同時(shí)添加一個(gè)UIPinchGestureRecognizer手勢(shì)
UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchAction:)];
pinch.delegate = self;
[_contentView addGestureRecognizer:pinch];
4 - 初始化刻度,最開(kāi)始的時(shí)候幽污,我設(shè)置的時(shí)間刻度最小單位是10分鐘
//計(jì)算需要多個(gè)刻度線
#define kJCTimeLineMaxHour 24
//最小刻度為10min嚷辅,也就是需要24*6個(gè)刻度
self.itemCount = 24 * 6;
//計(jì)算最小刻度寬,這里之所以減去self.width距误,是因?yàn)閏ontentview上時(shí)間刻度繪制區(qū)域是需要去掉頭部和尾部的空白區(qū)的,這個(gè)看控件的UI就能理解扁位,不多累贅了
CGFloat itemWidth = (self.contentViewWidth - self.width) / self.itemCount;
//畫(huà)刻度線
for (NSInteger i = 0; i < (self.itemCount+1); i++) {
CALayer *lineLayer = [CALayer layer];
lineLayer.backgroundColor = [self.timeLineDrawColor CGColor];
[self.contentView.layer addSublayer:lineLayer];
CGFloat height = 10;
if (i % 6 == 0) {
height = 25;//時(shí)刻度
}else if (i % (6/2) == 0){
height = 15;//中等刻度
}else{
height = 10;//最小刻度
}
lineLayer.frame = CGRectMake(self.startX + itemWidth * I,
self.height - kJCTimeLineBottomSpace - height,
1,
height);
}
接下來(lái)是繪制刻度文字
//因?yàn)槌跏硷@示區(qū)域較小准潭,這里暫且設(shè)置成每3小時(shí)繪制一次時(shí)間
//從00:00開(kāi)始一直到24:00,一共需要繪制9個(gè)時(shí)間點(diǎn)域仇,即當(dāng)時(shí)間分別為00:00刑然、03:00、06:00...時(shí)需要繪制文字
//下面計(jì)算在什么時(shí)候需要繪制
//計(jì)算方法為:時(shí)間范圍 / 最小刻度時(shí)間間隔
3小時(shí)*60分鐘 / 10分鐘 = 18格
//也就是每18格需要繪制一次
for (NSInteger i = 0; i < (self.itemCount+1); i++) {
//繪制刻度線
//...
//繪制時(shí)間文字
if (i % 18 == 0) {
NSInteger sec = i * 600;
CATextLayer *textLayer;
NSInteger stringWidth = 0;
NSString *string = [NSString stringWithFormat:@"%02ld:%02ld",(sec/3600),(sec%3600/60)];
CGSize stringSize = [string boundingRectWithSize:CGSizeMake(30, CGFLOAT_MAX) options:(NSStringDrawingUsesLineFragmentOrigin) attributes:self.timeLineTextAttributes context:nil].size;
stringWidth = stringSize.width;
textLayer = [[CATextLayer alloc] init];
textLayer.string = [[NSAttributedString alloc] initWithString:string
attributes:self.timeLineTextAttributes];
textLayer.contentsScale = [UIScreen mainScreen].scale;//寄宿圖的像素尺寸和視圖大小的比例,不設(shè)置為屏幕比例文字就會(huì)像素化
[self.contentView.layer addSublayer:textLayer];
textLayer.frame = CGRectMake((self.startX + itemWidth * i) - (stringWidth * 0.5),
self.height - kJCTimeLineBottomSpace,
stringWidth,
kJCTimeLineBottomSpace);
}
}
繪制已有時(shí)間區(qū)
//增加一個(gè)public屬性暇务,用于接受對(duì)外傳經(jīng)來(lái)的時(shí)間數(shù)組
/**
需要繪制的已有的時(shí)間
時(shí)間格式要求是xx:xx-xx:xx
起點(diǎn)時(shí)間-終點(diǎn)時(shí)間
*/
@property (nonatomic, strong) NSArray <NSString *> *timePaintingArray;
@property (nonatomic, strong) UIColor *timePaintingColor;
//繪制已有時(shí)間區(qū)
for (NSInteger i = 0; i < self.timePaintingArray.count; i++) {
NSString *timeRange = self.timePaintingArray[I];
NSString *startTime = [timeRange componentsSeparatedByString:@"-"].firstObject;
NSString *endTime = [timeRange componentsSeparatedByString:@"-"].lastObject;
//將時(shí)間轉(zhuǎn)成對(duì)應(yīng)的坐標(biāo)點(diǎn)
NSInteger startHourSec = [startTime componentsSeparatedByString:@":"][0].integerValue * 3600;
NSInteger startMinSec = [startTime componentsSeparatedByString:@":"][1].integerValue * 60;
NSInteger startSec = [startTime componentsSeparatedByString:@":"][2].integerValue;
startSec = startHourSec + startMinSec + startSec;
NSInteger endHourSec = [endTime componentsSeparatedByString:@":"][0].integerValue * 3600;
NSInteger endMinSec = [endTime componentsSeparatedByString:@":"][1].integerValue * 60;
NSInteger endSec = [endTime componentsSeparatedByString:@":"][2].integerValue;
endSec = endHourSec + endMinSec + endSec;
CALayer *timelayer = [[CALayer alloc] init];
timelayer.backgroundColor = [self.timePaintingColor CGColor];
[self.contentView.layer addSublayer:timelayer];
timelayer.frame = CGRectMake(self.startX + itemWidth * ((CGFloat)startSec / 600),
0,
(endSec - startSec) / (CGFloat)600 * itemWidth,
2 * kJCTimeLineBottomSpace);
}
此時(shí)刻度和時(shí)間文字都繪制出來(lái)了泼掠,至于中間的紅色指示線、底部的黑色線垦细,這個(gè)沒(méi)什么好說(shuō)的了择镇,隨便怎么實(shí)現(xiàn)都可以。
5 - 手勢(shì)捏合縮放
手勢(shì)捏合這里括改,主要是獲取到捏合縮放的系數(shù)后腻豌,直接改變contentView的frame,以及scrollView的contentSize
#pragma mark UIGestureRecognizerDelegate
// 允許多個(gè)手勢(shì)并發(fā)
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
#pragma mark - PinchActionHandler
- (void)pinchAction:(UIPinchGestureRecognizer *) sender{
if (sender.state == UIGestureRecognizerStateBegan ||
sender.state == UIGestureRecognizerStateChanged){
self.scrollView.scrollEnabled = NO;
UIView *view = [sender view];
//擴(kuò)大嘱能、縮小倍數(shù)
CGRect frame = view.frame;
frame.size.width = sender.scale * frame.size.width;
if (frame.size.width <= 2*self.width) {
//最小是2倍吝梅,和初始化的時(shí)候一樣
frame.size.width = 2*self.width;
}else if (frame.size.width >= 200*self.width){
//最大限制是200倍寬
frame.size.width = 200*self.width;
}
view.frame = frame;
self.scrollView.contentSize = frame.size;
self.contentViewWidth = frame.size.width;
sender.scale = 1;
//重新繪制刻度和時(shí)間文本等,最小刻度那些都要重新計(jì)算惹骂,具體代碼看Demo
[self reloadTimeLine];
self.scrollView.scrollEnabled = YES;
}
}
在縮放改變contentView的frame記憶scrollView的contentSize時(shí)苏携,中間的紅色位置也需要一起改變
//保持中間紅線位置不變
self.scrollView.contentOffset = CGPointMake(itemWidth * ((CGFloat)self.currentSec / (CGFloat)self.secUnit), 0);
這樣就實(shí)現(xiàn)了捏合放大和縮小
6 - 滾動(dòng)獲取時(shí)間
這里稍微做點(diǎn)限制,在捏合手勢(shì)的時(shí)候对粪,不獲取滾動(dòng)時(shí)間右冻,只有當(dāng)拖拽時(shí)候再去獲取滾動(dòng)的時(shí)間穿扳,直接上代碼。
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
if (self.isNeedScrollData) {
self.currentSec = scrollView.contentOffset.x / (self.contentViewWidth - self.width) * 86400;
if (self.currentSec <= 0) {
self.currentSec = 0;
}else if(self.currentSec >= 86400){
self.currentSec = 86400;
}
self.timeLabel.text = [NSString stringWithFormat:@"%02ld:%02ld:%02ld",self.currentSec/3600,self.currentSec%3600/60,self.currentSec%3600%60];
if (self.delegate &&
[self.delegate respondsToSelector:@selector(timeLine:scrollToTime:timeSecValue:)]) {
[self.delegate timeLine:self scrollToTime:self.timeLabel.text timeSecValue:self.currentSec];
}
}
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
self.isNeedScrollData = YES;
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
self.isNeedScrollData = decelerate;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
self.isNeedScrollData = NO;
}
7 - 優(yōu)化部分
主要是針對(duì)縮放時(shí)国旷,重新繪制刻度線和時(shí)間文本的優(yōu)化矛物。
我將縮放分為了6個(gè)區(qū)間模式,最小的刻度單位是10分鐘一格跪但,最大的是15秒一格
if (self.contentViewWidth < self.width*4) {
//每10分鐘1格
widthType = JCTimeLineWidthType10Min;
}else if (self.contentViewWidth >= self.width * 4 &&
self.contentViewWidth < self.width * 8){
//每5分鐘一格
widthType = JCTimeLineWidthType5Min;
}else if (self.contentViewWidth >= self.width * 8 &&
self.contentViewWidth < self.width * 18){
//每2分鐘一格
widthType = JCTimeLineWidthType2Min;
}else if (self.contentViewWidth >= self.width * 18 &&
self.contentViewWidth < self.width * 30){
//每1分鐘一格
widthType = JCTimeLineWidthType1Min;
}else if (self.contentViewWidth >= self.width * 30 &&
self.contentViewWidth < self.width * 150){
//每30秒一格
widthType = JCTimeLineWidthType30Sec;
}else{
//每15秒一格
widthType = JCTimeLineWidthType15Sec;
}
每次產(chǎn)生縮放履羞,重新繪制的時(shí)候,都會(huì)判斷下是否模式改變了屡久。
A - 對(duì)于刻度線和時(shí)間文本:
如果改變:
1 - 將已繪制添加上的刻度線和時(shí)間文字移除忆首;
2 - 需要重新創(chuàng)建刻度線的CALAyer和文字的CATextLayer,重新計(jì)算相應(yīng)的frame和時(shí)間文字被环;
3 - 緩存時(shí)間文字
沒(méi)改變:
1 - 只需要重新計(jì)算下frame糙及,改變frame就可以了
B - 對(duì)于已有的時(shí)間區(qū)
只需重新計(jì)算下frame就行了,在初始化的時(shí)候就已經(jīng)創(chuàng)建好了layer
上述優(yōu)化后筛欢,可以減少重新創(chuàng)建layer的開(kāi)銷浸锨。
此時(shí),會(huì)發(fā)現(xiàn)在縮放重新繪制時(shí)版姑,CPU開(kāi)銷還是比較高柱搜,特別是放到最大時(shí),因?yàn)槔L制的layer多剥险。只有在繪制layer的時(shí)候?qū)ayer的隱式動(dòng)畫(huà)關(guān)了就行聪蘸。
[CATransaction begin];
//這里執(zhí)行的代碼是沒(méi)有隱式動(dòng)畫(huà)的
[CATransaction setDisableActions:YES];
[CATransaction commit];