最近用UIBezierPath
繪制了一些圖形喻杈,像柱狀圖扫俺、折線圖和餅狀圖之類的圖形苍苞。先上效果圖:
基本原理:
- 利用
UIBezierPath
能夠創(chuàng)建基于矢量路徑的特性來繪制圖形的路徑,然后將UIBezierPath
和CAShapeLayer
建立關(guān)系狼纬,讓后者在前者提供的路徑中進(jìn)行渲染羹呵,最后生成我們所需的各種圖形。而且可以給CAShapeLayer
添加動畫特效疗琉。
一冈欢、柱狀圖
實現(xiàn)思路:
- 對數(shù)據(jù)源進(jìn)行分析,獲取數(shù)據(jù)源的最大值盈简,以及最大值和柱狀圖高度的比例涛癌,用于其他數(shù)據(jù)等比例顯示。數(shù)據(jù)源為:
- (NSArray *)topArray {
return @[@"342",@"900",@"505",@"1780",@"1450",@"30",@"1000”];
}
- (NSArray *)bottomArray {
return @[@"1月",@"2月",@"3月",@"4月",@"5月",@"6月",@"7月”];
}
獲取數(shù)據(jù)源最大值以及比例:
//獲取數(shù)據(jù)最大值
float max = [[self.topArray valueForKeyPath:@"@max.intValue"] floatValue];
//獲取比例大小
float scale = (K_HEIGHT-K_LABEL_HEIGHT*2)/max;
- 然后利用
UIBezierPath
繪制單個柱狀的起點和終點的連線:
//柱狀圖
UIBezierPath * bePath = [UIBezierPath bezierPath];
//起點
[bePath moveToPoint:CGPointMake(K_WIDTH/(count*2)+K_WIDTH/count*i, K_HEIGHT-K_LABEL_HEIGHT)];
//終點
[bePath addLineToPoint:CGPointMake(K_WIDTH/(count*2)+K_WIDTH/count*i, K_HEIGHT-K_LABEL_HEIGHT-[self.topArray[i] floatValue]*scale)];
[bePath stroke];
- 創(chuàng)建
CAShapeLayer
(其屬性作用在下面分類中有說明)送火,并與UIBezierPath
建立關(guān)系拳话,即設(shè)置path
屬性:
//添加CAShapeLayer
_shaLayer = [CAShapeLayer layerWithFillColor:[UIColor clearColor].CGColor strokeColor:[UIColor greenColor].CGColor strokeStart:0.0f strokeEnd:1.0f zPosition:1 lineWidth:30.0f path:bePath.CGPath];
[self.layer addSublayer:_shaLayer];
CAShapeLayer
的分類方法為:
@implementation CAShapeLayer (Category)
/** CAShapeLayer
* @param fillColor 填充顏色
* @param strokeColor 填充路徑的描邊輪廓的顏色
* @param strokeStart 表示路徑的起點,在[0,1]的范圍內(nèi)
* @param strokeEnd 表示路徑的終點,在[0,1]的范圍內(nèi)
* @param zPosition 表示在superlayer中的位置
* @param lineWidth 填充路徑的線寬
* @param path 表示要呈現(xiàn)形狀的路徑
*/
+ (CAShapeLayer *)layerWithFillColor:(CGColorRef)fillColor strokeColor:(CGColorRef)strokeColor strokeStart:(CGFloat)strokeStart strokeEnd:(CGFloat)strokeEnd zPosition:(CGFloat)zPosition lineWidth:(CGFloat)lineWidth path:(CGPathRef)path {
CAShapeLayer * layer = [CAShapeLayer layer];
layer.fillColor = fillColor;
layer.strokeColor = strokeColor;
layer.strokeStart = strokeStart;
layer.strokeEnd = strokeEnd;
layer.zPosition = zPosition;
layer.lineWidth = lineWidth;
layer.path = path;
return layer;
}
@end
- 為創(chuàng)建的
CAShapeLayer
添加動畫特效:
//動畫
- (void)startStroke {
[_shaLayer addAnimation:self.pathAnimation forKey:@"strokeEndAnimation”];
}
動畫方法為:
//動畫
- (CABasicAnimation *)pathAnimation {
CABasicAnimation * pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd”];
pathAnimation.duration = 2.0f;
pathAnimation.fromValue = @0.0f;//動畫開始位置
pathAnimation.toValue = @1.0f;//動畫停止位置
pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];//添加動畫樣式
return pathAnimation;
}
- 為每個柱狀創(chuàng)建上下數(shù)據(jù)展示
label
:
//上label
[self addSubview:[UILabel labelWithFrame:CGRectMake(K_WIDTH/count*i, K_HEIGHT-60-[self.topArray[i] floatValue]*scale, K_WIDTH/count, K_LABEL_HEIGHT) text:self.topArray[i] textColor:[UIColor redColor] textAlignment:NSTextAlignmentCenter font:15]];
//下label
[self addSubview:[UILabel labelWithFrame:CGRectMake(K_WIDTH/count*i, K_HEIGHT-K_LABEL_HEIGHT, K_WIDTH/count, K_LABEL_HEIGHT) text:self.bottomArray[i] textColor:[UIColor blackColor] textAlignment:NSTextAlignmentCenter font:13]];
- 最后按數(shù)據(jù)源個數(shù)對以上控件進(jìn)行循環(huán)創(chuàng)建。
最終實現(xiàn)效果為:
二种吸、折線圖
折線圖的實現(xiàn)方法和柱狀圖的實現(xiàn)方法類似弃衍,主要在于折線圖需要創(chuàng)建一個坐標(biāo)體系,并對數(shù)據(jù)源中每個坐標(biāo)點進(jìn)行連線繪制坚俗。直接上代碼(數(shù)據(jù)源和柱狀圖一樣):
#import “BrokenView.h”
#import "UILabel+Category.h”
#define K_WIDTH CGRectGetWidth(self.frame)
#define K_HEIGHT CGRectGetHeight(self.frame)
#define K_LABEL_HEIGHT 30
#define K_ACROSS_NUM 6 //橫線默認(rèn)條數(shù)
@implementation BrokenView {
CAShapeLayer * _shaLayer;
}
- (instancetype)initWithFrame:(CGRect)frame topArray:(NSArray *)topArray bottoArray:(NSArray *)bottomArray {
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor whiteColor];
self.topArray = topArray;
self.bottomArray = bottomArray;
}
return self;
}
//動畫
- (void)startStroke {
[_shaLayer addAnimation:self.pathAnimation forKey:@"strokeEndAnimation”];
}
- (void)drawRect:(CGRect)rect {
NSUInteger count = self.topArray.count;
if (count<1) return;
//獲取數(shù)據(jù)最大值
float max = [[self.topArray valueForKeyPath:@"@max.intValue"] floatValue];//獲取比例大小
float scale = (K_HEIGHT-K_LABEL_HEIGHT*2)/max;//上下兩個label高度和
//繪制坐標(biāo)系
for (int i=0; i<K_ACROSS_NUM; i++) {
//橫線
UIBezierPath * across = [UIBezierPath bezierPath];
[across moveToPoint:CGPointMake(0, (K_HEIGHT-K_LABEL_HEIGHT)/K_ACROSS_NUM*(i+1))];
[across addLineToPoint:CGPointMake(K_WIDTH, (K_HEIGHT-K_LABEL_HEIGHT)/K_ACROSS_NUM*(i+1))];
[[UIColor greenColor] set];
[across stroke];
}
for (int j=0; j<count; j++) {
//豎線
UIBezierPath * vertical = [UIBezierPath bezierPath];
[vertical moveToPoint:CGPointMake(K_WIDTH/(count*2)+K_WIDTH/count*j, K_HEIGHT-K_LABEL_HEIGHT)];
[vertical addLineToPoint:CGPointMake(K_WIDTH/(count*2)+K_WIDTH/count*j, 0)];
[[UIColor greenColor] set];
[vertical stroke];
//繪制各坐標(biāo)點
UIBezierPath * point = [UIBezierPath bezierPathWithArcCenter:CGPointMake(K_WIDTH/(count*2)+K_WIDTH/count*j, K_HEIGHT-K_LABEL_HEIGHT-[self.topArray[j] floatValue]*scale) radius:5.0f startAngle:-M_PI endAngle:M_PI*3 clockwise:YES];
CAShapeLayer * pointLayer = [CAShapeLayer layerWithFillColor:[UIColor blueColor].CGColor strokeColor:[UIColor clearColor].CGColor strokeStart:0.0f strokeEnd:1.0f zPosition:0 lineWidth:0.0f path:point.CGPath];
[self.layer addSublayer:pointLayer];
//上label
[self addSubview:[UILabel labelWithFrame:CGRectMake(K_WIDTH/count*j, K_HEIGHT-60-[self.topArray[j] floatValue]*scale, K_WIDTH/count, K_LABEL_HEIGHT) text:self.topArray[j] textColor:[UIColor redColor] textAlignment:NSTextAlignmentCenter font:15]];
//下label
[self addSubview:[UILabel labelWithFrame:CGRectMake(K_WIDTH/count*j, K_HEIGHT-K_LABEL_HEIGHT, K_WIDTH/count, K_LABEL_HEIGHT) text:self.bottomArray[j] textColor:[UIColor blackColor] textAlignment:NSTextAlignmentCenter font:13]];
}
//繪制折線
UIBezierPath * broPath = [UIBezierPath bezierPath];
[broPath moveToPoint:CGPointMake(K_WIDTH/(count*2), K_HEIGHT-K_LABEL_HEIGHT-[self.topArray[0] floatValue]*scale)];
for (int j=1; j<count; j++) {
[broPath addLineToPoint:CGPointMake(K_WIDTH/(count*2)+K_WIDTH/count*j, K_HEIGHT-K_LABEL_HEIGHT-[self.topArray[j] floatValue]*scale)];
}
[broPath stroke];
_shaLayer = [CAShapeLayer layer];
_shaLayer.lineWidth = 2.0f;
_shaLayer.fillColor = [UIColor clearColor].CGColor;
_shaLayer.strokeColor = [UIColor blueColor].CGColor;
_shaLayer.path = broPath.CGPath;
[self.layer addSublayer:_shaLayer];
[self startStroke];
}
@end
最終實現(xiàn)效果為:
三镜盯、餅狀圖
實現(xiàn)思路:
- 首先設(shè)置餅狀圖中心點以及半徑:
//設(shè)置餅狀圖中心點
CGFloat centerX = K_WIDTH * 0.5f;
CGFloat centerY = K_HEIGHT * 0.5f;
CGPoint centerPoint = CGPointMake(centerX, centerY);
//設(shè)置半徑
CGFloat radius = MIN(centerX, centerY) * 0.5f;//MIN(A,B)為獲取兩者最小值
- 設(shè)置數(shù)據(jù)源:
- (NSArray *)pieArray {
return @[@"70",@"60",@"100",@"50",@"80"];
}
- (NSArray *)colorArray {
return @[[UIColor redColor],[UIColor purpleColor],[UIColor blueColor],[UIColor orangeColor],[UIColor blackColor]];
}
- 獲取數(shù)據(jù)源數(shù)據(jù)總和岸裙,用于后面扇形劃分比例:
//獲取展示數(shù)據(jù)總和
CGFloat nums = 0.0f;
for (int i=0; i<self.dataArray.count; i++) {
nums += [self.dataArray[i] floatValue];
}
- 創(chuàng)建一個背景圓,用于后期添加動畫特效:
//繪制背景圓的路徑
UIBezierPath * backPath = [UIBezierPath bezierPathWithArcCenter:centerPoint
radius:radius
startAngle:-M_PI_2
endAngle:M_PI_2*3
clockwise:YES];
_backLayer = [CAShapeLayer layerWithFillColor:[UIColor clearColor].CGColor
strokeColor:[UIColor greenColor].CGColor
strokeStart:0.0f
strokeEnd:1.0f
zPosition:1
lineWidth:radius * 2.0f
path:backPath.CGPath];
UIBezierPath
繪制圓形方法+ (instancetype)bezierPathWithArcCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise;
中參數(shù)如下:center
是弧線中心點的坐標(biāo)速缆; radius
是弧線所在圓的半徑降允; startAngle
是弧線開始的角度值; endAngle
是弧線結(jié)束的角度值艺糜; clockwise
表示是否順時針畫弧線剧董。
- 用
UIBezierPath
繪制各個扇形的路徑,和背景圓路徑一樣:
//繪制各個扇形的路徑
UIBezierPath * subPath = [UIBezierPath bezierPathWithArcCenter:centerPoint
radius:radius
startAngle:-M_PI_2
endAngle:M_PI_2*3
clockwise:YES];
- 分別獲取餅狀圖中每個扇形的起點
strokeStart
和終點strokeEnd
破停,并按數(shù)據(jù)源的個數(shù)循環(huán)創(chuàng)建每個扇形的CAShapeLayer
翅楼,并與subPath
相關(guān)聯(lián),并以此獲取扇形的形狀真慢。
//設(shè)置各個扇形開始和結(jié)束位置
CGFloat start = 0.0f;
CGFloat end = 0.0f;
for (int i=0; i<self.dataArray.count; i++) {
end = [self.dataArray[i] floatValue]/nums + start;
CGColorRef strokeColor = (!self.colorArray || self.colorArray.count == 0 || i>self.colorArray.count-1) ? [UIColor purpleColor].CGColor : ((UIColor *)self.colorArray[i]).CGColor;
CAShapeLayer * subLayer = [CAShapeLayer layerWithFillColor:[UIColor clearColor].CGColor
strokeColor:strokeColor
strokeStart:start
strokeEnd:end
zPosition:2
lineWidth:radius * 2.0f
path:subPath.CGPath];
[self.layer addSublayer:subLayer];
//百分比label
CGFloat angle = M_PI * (start + end);//扇形角度
CGFloat labelCenterX = centerX * 0.5f * sinf(angle) + centerX;
CGFloat labelCenterY = -centerX * 0.5f * cosf(angle) + centerY;
UILabel * label = [UILabel labelWithFrame:CGRectMake(0, 0, radius * 0.8f, radius * 0.3f) text:[NSString stringWithFormat:@"%@ %ld%%",self.dataArray[i],(NSInteger)((end-start+0.005)*100)] textColor:[UIColor redColor] textAlignment:NSTextAlignmentCenter font:15];
label.center = CGPointMake(labelCenterX, labelCenterY);
label.backgroundColor = [UIColor whiteColor];
label.layer.zPosition = 3;
[self addSubview:label];
start = end;
}
其中以每個扇形的中軸線的中點為中心點來創(chuàng)建的label
用于顯示扇形的比例毅臊。中心點坐標(biāo)是利用三角形的正弦函數(shù)和余弦函數(shù)來確定的。
- 最后為背景圓的
Layer
添加動畫:
//動畫
- (void)startStroke {
[_backLayer addAnimation:self.pathAnimation forKey:@"circleAnimation”];
}
最終實現(xiàn)效果為:
Demo地址:UIBezierPath繪制柱狀圖黑界、折線圖和餅狀圖