iOS自定義滑動(dòng)喜庞、拖拽時(shí)間軸诀浪。仿螢石

仿照著做了一個(gè)時(shí)間軸的控件,類似螢石的效果延都,先上圖


時(shí)間軸效果.gif

實(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)都可以。


最小時(shí)間刻度為10分鐘效果.jpg
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)了捏合放大和縮小


捏合放大縮小.gif
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];

Demo下載

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市表制,隨后出現(xiàn)的幾起案子健爬,更是在濱河造成了極大的恐慌,老刑警劉巖么介,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件娜遵,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡夭拌,警方通過(guò)查閱死者的電腦和手機(jī)魔熏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)鸽扁,“玉大人蒜绽,你說(shuō)我怎么就攤上這事⊥跋郑” “怎么了躲雅?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)骡和。 經(jīng)常有香客問(wèn)我相赁,道長(zhǎng)相寇,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任钮科,我火速辦了婚禮唤衫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘绵脯。我一直安慰自己佳励,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布蛆挫。 她就那樣靜靜地躺著赃承,像睡著了一般。 火紅的嫁衣襯著肌膚如雪悴侵。 梳的紋絲不亂的頭發(fā)上瞧剖,一...
    開(kāi)封第一講書(shū)人閱讀 51,688評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音可免,去河邊找鬼抓于。 笑死,一個(gè)胖子當(dāng)著我的面吹牛巴元,可吹牛的內(nèi)容都是我干的毡咏。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼逮刨,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了堵泽?” 一聲冷哼從身側(cè)響起修己,我...
    開(kāi)封第一講書(shū)人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎迎罗,沒(méi)想到半個(gè)月后睬愤,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡纹安,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年尤辱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厢岂。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡光督,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出塔粒,到底是詐尸還是另有隱情结借,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布卒茬,位于F島的核電站船老,受9級(jí)特大地震影響咖熟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜柳畔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一馍管、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧薪韩,春花似錦确沸、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至岭洲,卻和暖如春宛逗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背盾剩。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工雷激, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人告私。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓屎暇,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親驻粟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子根悼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容