K線開發(fā)之K線整體搭建


前言

當(dāng)寫完了所有需要使用的素材類后乏屯,我們開始搭建一個(gè)比較完整的K線Demo漾月。

它包含以下功能:

1病梢、可以展示蠟燭圖
2、可以展示OHLC圖
3梁肿、可以左右滑動(dòng)
4蜓陌、可以長(zhǎng)按出現(xiàn)十字叉
5、有基本的價(jià)格和日期區(qū)間展示

GO

在上幾篇文章中吩蔑,我們已經(jīng)知道如何繪制蠟燭钮热、邊框、OHLC等烛芬。所以在這里隧期,可以直接使用已經(jīng)寫好的類。

繪制邊框

首頁赘娄,我們先繪制一個(gè)包含主副圖的邊框:

/**
 繪制邊框
 */
- (void)drawBorder
{
    //設(shè)置主圖仆潮、主圖指標(biāo)、副圖遣臼、副圖指標(biāo)rect
    _mainIndexRect = CGRectMake(0, 0, CGRectGetWidth(self.frame), mainIndexH);
    _mainRect = CGRectMake(0, mainIndexH, CGRectGetWidth(self.frame), (CGRectGetHeight(self.frame) - (mainIndexH + accessoryIndexH + dateH)) * mainFrameScale);
    _accessoryIndexRect = CGRectMake(0, mainIndexH + CGRectGetHeight(_mainRect)+dateH, CGRectGetWidth(self.frame), accessoryIndexH);
    _accessoryRect = CGRectMake(0, mainIndexH + CGRectGetHeight(_mainRect)+dateH+accessoryIndexH, CGRectGetWidth(self.frame), (CGRectGetHeight(self.frame) - (mainIndexH + accessoryIndexH + dateH)) * (1-mainFrameScale));
    
    
    CAShapeLayer *borderLayer = [CAShapeLayer layer];
    UIBezierPath *path = [UIBezierPath bezierPathWithRect:self.bounds];
    
    [path moveToPoint:CGPointMake(0, mainIndexH)];
    [path addLineToPoint:CGPointMake(CGRectGetWidth(self.frame), mainIndexH)];
    
    [path moveToPoint:CGPointMake(0, CGRectGetMaxY(_mainRect))];
    [path addLineToPoint:CGPointMake(CGRectGetWidth(self.frame), CGRectGetMaxY(_mainRect))];
    
    [path moveToPoint:CGPointMake(0, CGRectGetMinY(_accessoryIndexRect))];
    [path addLineToPoint:CGPointMake(CGRectGetWidth(self.frame), CGRectGetMinY(_accessoryIndexRect))];
    
    [path moveToPoint:CGPointMake(0, CGRectGetMinY(_accessoryRect))];
    [path addLineToPoint:CGPointMake(CGRectGetWidth(self.frame), CGRectGetMinY(_accessoryRect))];
    
    float mainUnitH = CGRectGetHeight(_mainRect) / 4.f;
    float mainUnitW = CGRectGetWidth(_mainRect) / 4.f;
    
    for (int idx = 1; idx <= 3; idx++)
    {
        //畫3條橫線
        [path moveToPoint:CGPointMake(0, mainIndexH + mainUnitH * idx)];
        [path addLineToPoint:CGPointMake(CGRectGetWidth(self.frame), mainIndexH + mainUnitH * idx)];
        
        //畫3條豎線
        [path moveToPoint:CGPointMake(idx * mainUnitW, mainIndexH)];
        [path addLineToPoint:CGPointMake(idx * mainUnitW, CGRectGetMaxY(_mainRect))];
        
        //畫3條豎線
        [path moveToPoint:CGPointMake(idx * mainUnitW, CGRectGetMinY(_accessoryRect))];
        [path addLineToPoint:CGPointMake(idx * mainUnitW, CGRectGetMaxY(_accessoryRect))];
    }

    float accessoryUnitH = CGRectGetHeight(_accessoryRect) / 2.f;
    [path moveToPoint:CGPointMake(0, CGRectGetMaxY(_accessoryRect) - accessoryUnitH)];
    [path addLineToPoint:CGPointMake(CGRectGetWidth(self.frame), CGRectGetMaxY(_accessoryRect) - accessoryUnitH)];
    
    borderLayer.path = path.CGPath;
    borderLayer.lineWidth = 0.5f;
    borderLayer.strokeColor = [UIColor colorWithRed:222.f/255.f green:222.f/255.f blue:222.f/255.f alpha:1.f].CGColor;
    borderLayer.fillColor = [UIColor clearColor].CGColor;
    
    [self.layer addSublayer:borderLayer];
    
}

效果如下:


邊框

導(dǎo)入數(shù)據(jù)

邊框繪制完以后性置,我們把k線數(shù)據(jù)轉(zhuǎn)換為坐標(biāo)點(diǎn)。在轉(zhuǎn)換之前揍堰,還需要尋找當(dāng)前在屏幕上展示數(shù)據(jù)的極限值鹏浅,也就是最大最小值。

這里要注意屏歹,因?yàn)楫?dāng)前屏幕中一般最多顯示60個(gè)左右的蠟燭數(shù)量篡石,但是某一個(gè)周期K線的蠟燭數(shù)量一般是幾百個(gè),所以在K線數(shù)據(jù)轉(zhuǎn)為模型數(shù)據(jù)后西采,需要有一個(gè)起始索引來標(biāo)識(shí)當(dāng)前展示的數(shù)據(jù)范圍。

尋找極限值:

//求出最大最小值
    _min = (float)INT32_MAX;
    _max = (float)INT32_MIN;
    for (int idx=_startIndex; idx<_endIndex; idx++)
    {
        YKKLineModel *model = self.kLineModelArr[idx];
        if (_min > model.low)
        {
            _min = model.low;
        }
        if (_max < model.high)
        {
            _max = model.high;
        }
    }

把數(shù)據(jù)轉(zhuǎn)換為坐標(biāo)點(diǎn):

    [self.displayPointArr removeAllObjects];
    //每根蠟燭的寬度
    float candleW = CGRectGetWidth(_mainRect) / candleCount;
    for (int idx = _startIndex; idx<_endIndex; idx++)
    {
        YKKLineModel *model = self.kLineModelArr[idx];
        float x = CGRectGetMinX(_mainRect) + candleW * (idx - (_startIndex - 0));
     
        CGPoint hPoint = CGPointMake(x + candleW/2,
                                     ABS(CGRectGetMaxY(_mainRect) - (model.high  - _min)/unitValue));
        CGPoint lPoint = CGPointMake(x + candleW/2,
                                     ABS(CGRectGetMaxY(_mainRect) - (model.low   - _min)/unitValue));
        CGPoint oPoint = CGPointMake(x + candleW/2,
                                     ABS(CGRectGetMaxY(_mainRect) - (model.open  - _min)/unitValue));
        CGPoint cPoint = CGPointMake(x + candleW/2,
                                     ABS(CGRectGetMaxY(_mainRect) - (model.close - _min)/unitValue));
        [_displayPointArr addObject:[YKCandlePointModel candlePointModelWithOpoint:oPoint
                                                                            Hpoint:hPoint
                                                                            Lpoint:lPoint
                                                                            Cpoint:cPoint]];
    }

繪制蠟燭/OHLC

導(dǎo)入數(shù)據(jù)并且轉(zhuǎn)換為坐標(biāo)點(diǎn)以后继控,接下來開始繪制械馆。

繪制蠟燭:

    //每根蠟燭的寬度
    float candleW = CGRectGetWidth(_mainRect) / candleCount;
    
    for (int idx = 0; idx< candleCount; idx++)
    {
        YKCandlePointModel *model = pointModelArr[idx];
        CAShapeLayer *layer = [CAShapeLayer getCandleLayerWithPointModel:model candleW:candleW];
        
        [self.candleLayer addSublayer:layer];
    }
    
    [self.layer addSublayer:self.candleLayer];

繪制OHLC:

    //每根OHLC的寬度
    float candleW = CGRectGetWidth(_mainRect) / candleCount;
    
    for (int idx = 0; idx< candleCount; idx++)
    {
        YKCandlePointModel *model = pointModelArr[idx];
        CAShapeLayer *layer = [CAShapeLayer getOHLCLayerWithPointModel:model candleW:candleW];
        
        [self.ohlcLayer addSublayer:layer];
    }
    
    [self.layer addSublayer:self.ohlcLayer];

效果如下:

蠟燭
OHLC

繪制價(jià)格、日期區(qū)間

蠟燭繪制完以后武通,我們還剩下價(jià)格霹崎、日期。價(jià)格是指左側(cè)的5個(gè)價(jià)格的區(qū)間標(biāo)識(shí)冶忱,從上往下降序排列尾菇;日期為下方5個(gè)日期的區(qū)間標(biāo)識(shí),每一個(gè)標(biāo)識(shí)都是距離當(dāng)前點(diǎn)最近的點(diǎn)的日期。

繪制左側(cè)價(jià)格:

    float unitPrice = (_max - _min) / 4.f;
    float unitH = CGRectGetHeight(_mainRect) / 4.f;
    
    //求得價(jià)格rect
    NSDictionary *attribute = @{NSFontAttributeName:[UIFont systemFontOfSize:9.f]};
    CGRect priceRect = [self rectOfNSString:[NSString stringWithFormat:@"%.2f", _max] attribute:attribute];
    
    for (int idx = 0; idx < 5; idx++)
    {
        float height = 0.f;
        if (idx == 4)
        {
            height = idx * unitH - CGRectGetHeight(priceRect);
        } else
        {
            height = idx * unitH;
        }
        CGRect rect = CGRectMake(CGRectGetMinX(_mainRect),
                                 CGRectGetMinY(_mainRect) + height,
                                 CGRectGetWidth(priceRect),
                                 CGRectGetHeight(priceRect));
        //計(jì)算價(jià)格
        NSString *str = [NSString stringWithFormat:@"%.2f", _max - idx * unitPrice];
        CATextLayer *layer = [CATextLayer getTextLayerWithString:str
                                                       textColor:[UIColor blackColor]
                                                        fontSize:9.f
                                                 backgroundColor:[UIColor clearColor]
                                                           frame:rect];
        
        [self.leftPriceLayer addSublayer:layer];
    }
    
    [self.layer addSublayer:self.leftPriceLayer];

繪制日期(這里要注意派诬,因?yàn)槭褂玫腄emo數(shù)據(jù)周期為天劳淆,所以日期也具體到天):

    NSMutableArray *kLineDateArr = [NSMutableArray array];
    int unitCount = candleCount / 4;
    for (int idx=0; idx<5; idx++)
    {
        YKKLineModel *model = self.kLineModelArr[_startIndex + idx * unitCount];
        
        NSDate *detaildate = [NSDate dateWithTimeIntervalSince1970:model.timeStamp];
        NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
        [dateFormatter setDateFormat:@"YYYY-MM-dd"];
        NSString *dateStr = [dateFormatter stringFromDate:detaildate];
        
        [kLineDateArr addObject:dateStr];
    }
    
    NSDictionary *attribute = @{NSFontAttributeName:[UIFont systemFontOfSize:9.f]};
    CGRect strRect = [self rectOfNSString:@"0000-00-00" attribute:attribute];
    float strW = CGRectGetWidth(strRect);
    float strH = CGRectGetHeight(strRect);
    
    float unitW = CGRectGetWidth(_mainRect) / 4;
    
    //循環(huán)繪制坐標(biāo)點(diǎn)
    for (int idx = 0; idx < kLineDateArr.count; idx++)
    {
        CATextLayer *textLayer = nil;
        
        if (idx == kLineDateArr.count-1)
        {//最后一個(gè)
            CGRect rect = CGRectMake(idx * unitW - strW, CGRectGetMaxY(_mainRect), strW, strH);
            textLayer = [CATextLayer getTextLayerWithString:kLineDateArr[idx] textColor:[UIColor blackColor] fontSize:9.f backgroundColor:[UIColor clearColor] frame:rect];
        }else if(idx == 0)
        {//第一個(gè)
            CGRect rect = CGRectMake(idx * unitW, CGRectGetMaxY(_mainRect), strW, strH);
            textLayer = [CATextLayer getTextLayerWithString:kLineDateArr[idx] textColor:[UIColor blackColor] fontSize:9.f backgroundColor:[UIColor clearColor] frame:rect];
        }else
        {//中間
            CGRect rect = CGRectMake(idx * unitW - strW/2, CGRectGetMaxY(_mainRect), strW, strH);
            textLayer = [CATextLayer getTextLayerWithString:kLineDateArr[idx] textColor:[UIColor blackColor] fontSize:9.f backgroundColor:[UIColor clearColor] frame:rect];
        }
        
        [self.bottomDateLayer addSublayer:textLayer];
    }
    
    [self.layer addSublayer:self.bottomDateLayer];

效果如下:

區(qū)間標(biāo)識(shí)

添加左右滑動(dòng)

在以前我們討論過滑動(dòng)偏移量的獲取方式,這里就暫且通過添加手勢(shì)來獲取偏移量默赂。使用長(zhǎng)按手勢(shì)來做十字叉效果沛鸵,使用拖動(dòng)手勢(shì)來做左右滑動(dòng)效果。

當(dāng)檢測(cè)到用戶長(zhǎng)按時(shí)缆八,獲取坐標(biāo)點(diǎn)然后轉(zhuǎn)換為柱子索引曲掰,再繪制十字叉,左側(cè)和下側(cè)展示當(dāng)前索引的數(shù)據(jù)奈辰。當(dāng)用戶抬起手指時(shí)栏妖,可以選擇及時(shí)清除掉十字叉,也可以加一個(gè)延時(shí)清除奖恰。

當(dāng)檢測(cè)到用戶拖動(dòng)時(shí)吊趾,用偏移量的正負(fù)來判斷用戶是向左還是向右拖動(dòng)。每次檢測(cè)到拖動(dòng)后房官,獲取到偏移量趾徽,因?yàn)檫@個(gè)偏移量不是太線性,所以添加一個(gè)范圍的判斷翰守。拿到偏移量以后孵奶,再更新展示數(shù)據(jù)的起始索引值,然后再更新視圖蜡峰。

添加手勢(shì):

    //添加左右拖動(dòng)手勢(shì)
    UIPanGestureRecognizer *panG = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureAction:)];
    [_kLineView addGestureRecognizer:panG];
    
    //添加長(zhǎng)按手勢(shì)
    UILongPressGestureRecognizer *longG = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(kLineLongGestureAction:)];
    longG.minimumPressDuration = 0.5f;
    longG.numberOfTouchesRequired = 1;
    [_kLineView addGestureRecognizer:longG];

響應(yīng)手勢(shì):

/**
 K線響應(yīng)長(zhǎng)按手勢(shì)
 
 @param longGesture 手勢(shì)對(duì)象
 */
- (void)kLineLongGestureAction:(UILongPressGestureRecognizer *)longGesture
{
    if (longGesture.state == UIGestureRecognizerStateBegan || longGesture.state == UIGestureRecognizerStateChanged)
    {
        CGPoint point = [longGesture locationInView:_kLineView];
        
        float x = 0.f;
        if (point.x < 0.f)
        {
            x = 0.f;
        }else if (point.x > CGRectGetWidth(_kLineView.frame))
        {
            x = CGRectGetWidth(_kLineView.frame)-1;
        }else
        {
            x = point.x;
        }
        //當(dāng)長(zhǎng)按滑動(dòng)時(shí)了袁,每滑動(dòng)一次話會(huì)重新刷新十字叉
        [_kLineView drawCrossViewWithX:x];
    }else
    {
        //當(dāng)手指抬起時(shí),及時(shí)把十字叉取消掉
        [_kLineView clearCrossViewLayer];
    }
}

/**
 響應(yīng)拖動(dòng)手勢(shì)
 
 @param panGesture 手勢(shì)對(duì)象
 */
- (void)panGestureAction:(UIPanGestureRecognizer *)panGesture
{
    CGPoint point = [panGesture translationInView:_kLineView];
    float offset =  point.x - kLineGlobalOffset;
    if (panGesture.state == UIGestureRecognizerStateChanged && ABS(offset) > 3)
    {
        if (offset > 0)
        {
            if (ABS(offset) > 20)
            {
                [_kLineView dragRightOffsetcount:5];
                
            } else if(ABS(offset) > 6)
            {
                [_kLineView dragRightOffsetcount:2];
                
            } else
            {
                [_kLineView dragRightOffsetcount:1];
            }
        }else
        {
            if (ABS(offset) > 20)
            {
                [_kLineView dragLeftOffsetcount:5];
                
            } else if(ABS(offset) > 6)
            {
                [_kLineView dragLeftOffsetcount:2];
                
            } else
            {
                [_kLineView dragLeftOffsetcount:1];
            }
        }
        kLineGlobalOffset = point.x;
    }
    
    if (panGesture.state == UIGestureRecognizerStateEnded ||
        panGesture.state == UIGestureRecognizerStateCancelled ||
        panGesture.state == UIGestureRecognizerStateFailed)
    {
        kLineGlobalOffset= 0.f;
    }
}

效果如下:

OHLC線

Demo源碼下載

至此湿颅,我們已經(jīng)把K線主圖大部分的功能搭建完畢载绿。Demo源碼點(diǎn)擊這里下載。下篇文章油航,會(huì)說到主副圖指標(biāo)的一些事崭庸,敬請(qǐng)期待。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末谊囚,一起剝皮案震驚了整個(gè)濱河市怕享,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌镰踏,老刑警劉巖函筋,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異奠伪,居然都是意外死亡跌帐,警方通過查閱死者的電腦和手機(jī)首懈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谨敛,“玉大人究履,你說我怎么就攤上這事∮逗校” “怎么了挎袜?”我有些...
    開封第一講書人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)肥惭。 經(jīng)常有香客問我盯仪,道長(zhǎng),這世上最難降的妖魔是什么蜜葱? 我笑而不...
    開封第一講書人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任全景,我火速辦了婚禮,結(jié)果婚禮上牵囤,老公的妹妹穿的比我還像新娘爸黄。我一直安慰自己,他們只是感情好揭鳞,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開白布炕贵。 她就那樣靜靜地躺著,像睡著了一般野崇。 火紅的嫁衣襯著肌膚如雪称开。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,562評(píng)論 1 305
  • 那天乓梨,我揣著相機(jī)與錄音鳖轰,去河邊找鬼。 笑死扶镀,一個(gè)胖子當(dāng)著我的面吹牛蕴侣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播臭觉,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼昆雀,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了蝠筑?” 一聲冷哼從身側(cè)響起忆肾,我...
    開封第一講書人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎菱肖,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體旭从,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡稳强,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年场仲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片退疫。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡渠缕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出褒繁,到底是詐尸還是另有隱情亦鳞,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布棒坏,位于F島的核電站燕差,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏坝冕。R本人自食惡果不足惜徒探,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望喂窟。 院中可真熱鬧测暗,春花似錦、人聲如沸磨澡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽稳摄。三九已至稚字,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間秩命,已是汗流浹背尉共。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留弃锐,地道東北人袄友。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像霹菊,于是被迫代替她去往敵國和親剧蚣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,139評(píng)論 25 707
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫旋廷、插件鸠按、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,103評(píng)論 4 62
  • 深夜十二點(diǎn)目尖,白蕓回到家,臉上掛著淚痕扎运,男友還在呼呼大睡瑟曲,躡手躡腳走到浴室饮戳。 浴室水聲淅淅瀝瀝,她用香皂使勁擦洗身子...
    唯美成殤閱讀 37,312評(píng)論 0 0
  • 故鄉(xiāng)是一個(gè)很難去提筆書寫的命題洞拨,總是帶給人一種忽明忽暗忽遠(yuǎn)忽近的迷離感扯罐。到現(xiàn)在為止,我對(duì)故鄉(xiāng)的情感總是有一些淡淡的...
    冬肆閱讀 235評(píng)論 0 0
  • 花王泡沫染發(fā)劑新手使用后效果 如果你想染發(fā)但又沒什么錢去理發(fā)店染烦衣,那你可以動(dòng)手自己試試歹河,絕對(duì)不會(huì)讓你失望的。 下面...
    果果99閱讀 79,204評(píng)論 0 5