本篇主要从以下几个方面来写的一点东西:
- 线段
- 曲线
- 动画
- 简单的柱状图
- 简单的折线图
线段
- 单线段
两点确定一条直线,给贝塞尔曲线一个起始点moveToPoint
再添加一条线的终点addLineToPoint
,这样就确定了一条直线。
- (void)drawLine {
UIView *view = [self.view viewWithTag:1024];
UILabel *label = [view viewWithTag:524];
label.text = @"直线";
CAShapeLayer *line = [CAShapeLayer layer];
line.lineWidth = 2;
line.strokeColor = [UIColor orangeColor].CGColor;
line.fillColor = nil;
[view.layer addSublayer:line];
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
[bezierPath moveToPoint:CGPointMake(100, 50)];
[bezierPath addLineToPoint:CGPointMake(200, 150)];
line.path = bezierPath.CGPath;
}
- 多线段
前面线段的终点是后面线段的起点。给一个起点moveToPoint
,然后想添加几条线就给几个线的终点addLineToPoint
。
- (void)drawDoubleLine {
UIView *view = [self.view viewWithTag:1025];
UILabel *label = [view viewWithTag:525];
label.text = @"折线";
CAShapeLayer *line = [CAShapeLayer layer];
line.lineWidth = 2;
line.strokeColor = [UIColor orangeColor].CGColor;
line.fillColor = nil;
[view.layer addSublayer:line];
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
[bezierPath moveToPoint:CGPointMake(100, 50)];
[bezierPath addLineToPoint:CGPointMake(200, 150)];
[bezierPath addLineToPoint:CGPointMake(200, 100)];
[bezierPath addLineToPoint:CGPointMake(250, 150)];
line.path = bezierPath.CGPath;
}
- 闭合多边形
也是多线段连起来的,只不过最后一条线的终点为第一条线段的起点。
- (void)drawTriangle {
UIView *view = [self.view viewWithTag:1026];
UILabel *label = [view viewWithTag:526];
label.text = @"闭合多边形";
CAShapeLayer *triangle = [CAShapeLayer layer];
triangle.lineWidth = 2;
triangle.strokeColor = [UIColor redColor].CGColor;
triangle.fillColor = [UIColor clearColor].CGColor;
[view.layer addSublayer:triangle];
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
[bezierPath moveToPoint:CGPointMake(kDeviceWidth/2.0, 50)];
[bezierPath addLineToPoint:CGPointMake(kDeviceWidth/2.0-100, 150)];
[bezierPath addLineToPoint:CGPointMake(kDeviceWidth/2.0+100, 150)];
[bezierPath addLineToPoint:CGPointMake(kDeviceWidth/2.0, 50)];
triangle.path = bezierPath.CGPath;
}
-
线端点样式
CAShapeLayer的lineCap
属性决定线端点样式,可选样式kCALineCapButt(默认)
,kCALineCapRound(圆角)
,kCALineCapSquare(平角)
。默认为kCALineCapButt
也是平角。
-
线段拐点处样式
CAShapeLayer的lineJoin
属性决定线端点样式,可选样式kCALineJoinMiter(尖角)
,kCALineJoinRound(圆角)
,kCALineJoinBevel(平角)
。默认为kCALineJoinMiter
。
虚线
@property(nullable, copy) NSArray *lineDashPattern;
CAShapeLayer的lineDashPattern
属性决定你画出一条什么样的虚线,这个属性返回一组NSNumber
类型的数组,其实就是实虚相交
来表示你的虚线,数组的长度由你决定(当然最好不要第一轮实虚相加超过线段长度)。比如line.lineDashPattern = @[@10,@5,@2,@8];
就是表示每轮都为长度为10的实线,长度为5的虚线,长度为2的实线,长度为8的虚线,循环直到线段结束。
曲线
-
二次贝塞尔曲线
二次贝塞尔曲线有一个控制点,控制点的位置决定了显示一条怎样的曲线。下面的例子,我把起点pA、终点pB、控制点pC 都画出来方便观察。
//篇幅限制 只贴主要代码
//曲线
CAShapeLayer *layerOne = [CAShapeLayer layer];
layerOne.fillColor = [UIColor clearColor].CGColor;
layerOne.strokeColor = [UIColor blackColor].CGColor;
layerOne.strokeStart = 0;
layerOne.strokeEnd = 1;
[view.layer addSublayer:layerOne];
//路径
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:pA];
[path addQuadCurveToPoint:pB controlPoint:pC];
//关联路径
layerOne.path = path.CGPath;
-
三次贝塞尔曲线
三次贝塞尔曲线有两个控制点,两个控制点的位置决定了显示一条怎样的曲线。下面的例子,我把起点pA、终点pB、控制点pC、pD 都画出来方便观察。
//篇幅限制 只贴主要代码
//曲线
CAShapeLayer *layerTwo = [CAShapeLayer layer];
layerTwo.fillColor = [UIColor clearColor].CGColor;
layerTwo.strokeColor = [UIColor blackColor].CGColor;
layerTwo.strokeStart = 0;
layerTwo.strokeEnd = 1;
[view.layer addSublayer:layerTwo];
//路径
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:pA];
[path addCurveToPoint:pB controlPoint1:pC controlPoint2:pD];
//关联路径
layerTwo.path = path.CGPath;
- 圆角矩形
- (void)drawRectRound {
UIView *view = [self.view viewWithTag:1028];
UIBezierPath *rectRound = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(kDeviceWidth/2.0-100, 50, 200, 100) byRoundingCorners:UIRectCornerBottomLeft | UIRectCornerBottomRight cornerRadii:CGSizeMake(20, 20)];
CAShapeLayer *layer = [CAShapeLayer layer];
layer.strokeColor = [UIColor clearColor].CGColor;
layer.fillColor = [UIColor whiteColor].CGColor;
layer.path = rectRound.CGPath;
[view.layer addSublayer:layer];
}
- 虚线圆
如果是静态的(无动画),那么需要两个贝塞尔圆环曲线表示内圆和外圆,内圆一周,外圆实时进度。
如果是动态的(有动画),那么可以一个贝塞尔圆环曲线表示内圆和外圆,内、外圆都一周,外圆添加动画,动画的toValue
标志实时进度。
- (void)drawXuCircle {
UIView *view = [self.view viewWithTag:1029];
//底部虚圆
CAShapeLayer *xuCircle = [CAShapeLayer layer];
xuCircle.lineWidth = 10;
xuCircle.strokeColor = ColorWithHex(0xbebebe, 1).CGColor;
xuCircle.fillColor = nil;
xuCircle.lineJoin = kCALineJoinMiter;
xuCircle.lineDashPattern = @[@2,@3];
[view.layer addSublayer:xuCircle];
//外部虚圆
CAShapeLayer *circle = [CAShapeLayer layer];
circle.lineWidth = 10;
circle.strokeColor = ColorWithHex(0xa2d100, 1).CGColor;
circle.fillColor = nil;
circle.lineJoin = kCALineJoinMiter;
circle.lineDashPattern = @[@2,@3];
[view.layer addSublayer:circle];
UIBezierPath *xuBezierPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(kDeviceWidth/2.0, 100) radius:55 startAngle:-M_PI_2 endAngle:3*M_PI_2 clockwise:YES];
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(kDeviceWidth/2.0, 100) radius:55 startAngle:-M_PI_2 endAngle:M_PI_2 clockwise:YES];
xuCircle.path = xuBezierPath.CGPath;
circle.path = bezierPath.CGPath;
}
动画
现在我们来给一些图形加上动画,使运行起来更美观。
- 主要写了三类动画
1.最常用的普通动画
2.进度条动画
3.其他属性的动画(比如这里有重复次数和逆执行)
//普通动画,strokeEnd
- (CABasicAnimation *)animComm {
if (_animComm == nil) {
_animComm = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
_animComm.fromValue = @0.0;
_animComm.toValue = @1.0;
_animComm.duration = 2.0;
}
return _animComm;
}
//进度条动画
- (CABasicAnimation *)animProgress {
if (_animProgress == nil) {
_animProgress = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
_animProgress.fromValue = @0.0;
_animProgress.toValue = @0.7;
_animProgress.fillMode = kCAFillModeForwards;
_animProgress.removedOnCompletion = NO;
_animProgress.duration = 2.0;
}
return _animProgress;
}
//重复次数,逆执行试用
- (CABasicAnimation *)animRepeat {
if (_animRepeat == nil) {
_animRepeat = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
_animRepeat.fromValue = @0.0;
_animRepeat.toValue = @1.0;
_animRepeat.duration = 2.0;
_animRepeat.autoreverses = YES;
_animRepeat.repeatCount = 10;
}
return _animRepeat;
}
简单的柱状图
这是个非常简单的柱状图,需要注意的是柱子的三个重要部分,起点、终点、柱宽。柱子由起点根据柱宽向左右两边扩张,如下图柱子的起点是位置2而不是位置1。
#import "SJBarChart.h"
static CGFloat const lineWidth = 1.0; //坐标轴线宽
static CGFloat const distance = 20.0; //距屏幕边距
static CGFloat const cornerW = 10.0f; //拐角长度
static CGFloat const barWidth = 50.0f; //柱状宽度
static CGFloat const space = 30.0f; //柱状之间的间隔
static CGFloat const scale = 3.0f; //柱状显示高度计算比例 *scale
@interface SJBarChart ()
{
CGFloat selfW, selfH;
NSArray *source;
}
@property (nonatomic, strong) CAShapeLayer *xAxis;
@property (nonatomic, strong) CAShapeLayer *yAxis;
@property (nonatomic, strong) UIScrollView *barScrollView;
@property (nonatomic, strong) CABasicAnimation *animation;
@end
@implementation SJBarChart
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
//
selfW = frame.size.width;
selfH = frame.size.height;
self.backgroundColor = [UIColor lightGrayColor];
}
return self;
}
- (void)showBarChart:(NSArray *)sourceArray {
source = sourceArray;
[self addxyAxis];
[self addSubview:self.barScrollView];
_barScrollView.contentSize = CGSizeMake(sourceArray.count*(space+barWidth) + space, 0);
[sourceArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
CAShapeLayer *bar = [self drawBar:idx];
[_barScrollView.layer addSublayer:bar];
}];
}
//柱状图
- (CAShapeLayer *)drawBar:(NSInteger)index {
CAShapeLayer *layer = [CAShapeLayer layer];
layer.fillColor = [UIColor clearColor].CGColor;
layer.strokeColor = [UIColor redColor].CGColor;
layer.lineWidth = barWidth;
//终点y
CGFloat y = _barScrollView.frame.size.height-60 - lineWidth/2.0 - ([[source objectAtIndex:index] floatValue] * scale);
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake((space + barWidth)*index + (space+barWidth/2.0), _barScrollView.frame.size.height-60)];
[path addLineToPoint:CGPointMake((space + barWidth)*index + (space+barWidth/2.0), y)];
layer.path = path.CGPath;
[layer addAnimation:self.animation forKey:nil];
return layer;
}
//添加坐标轴
- (void)addxyAxis {
self.xAxis = [self lineWithStartPoint:CGPointMake(distance, selfH-30) breakPoint:CGPointMake(kDeviceWidth-distance, selfH-30) endPoint:CGPointMake(kDeviceWidth-distance-cornerW, selfH-30-cornerW)];
self.yAxis = [self lineWithStartPoint:CGPointMake(distance+lineWidth/2.0, selfH-30) breakPoint:CGPointMake(distance, 30) endPoint:CGPointMake(distance+cornerW, 30+cornerW)];
[self.layer addSublayer:self.xAxis];
[self.layer addSublayer:self.yAxis];
}
//画坐标轴
- (CAShapeLayer *)lineWithStartPoint:(CGPoint)startPoint breakPoint:(CGPoint)breakPoint endPoint:(CGPoint)endPoint {
CAShapeLayer *line = [CAShapeLayer layer];
line.fillColor = [UIColor clearColor].CGColor;
line.strokeColor = [UIColor blackColor].CGColor;
line.lineWidth = 1.0;
UIBezierPath *linePath = [UIBezierPath bezierPath];
[linePath moveToPoint:startPoint];
[linePath addLineToPoint:breakPoint];
[linePath addLineToPoint:endPoint];
line.path = linePath.CGPath;
[line addAnimation:self.animation forKey:@"xyLineStrokeEndAnimation"];
return line;
}
- (UIScrollView *)barScrollView {
if (_barScrollView == nil) {
_barScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(distance+lineWidth, 30, kDeviceWidth-distance*2-lineWidth-cornerW, selfH-60-lineWidth/2.0)];
_barScrollView.bounces = NO;
_barScrollView.showsHorizontalScrollIndicator = NO;
}
return _barScrollView;
}
- (CABasicAnimation *)animation {
if (_animation == nil) {
_animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
_animation.fromValue = @0.0;
_animation.toValue = @1.0;
_animation.duration = 2.0;
_animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
}
return _animation;
}
@end
折线图
柱状图是一条条单独的线段,折线图就是一条连起来的完整折线。
#import "SJLineChart.h"
static CGFloat const lineWidth = 1.0; //坐标轴线宽
static CGFloat const distance = 20.0; //距屏幕边距
static CGFloat const cornerW = 10.0f; //拐角长度
static CGFloat const space = 50.0f; //柱状之间的间隔
static CGFloat const scale = 3.0f; //直线显示高度计算比例 *scale
static CGFloat const radius = 3.0f; //标记每个点的小圆半径
@interface SJLineChart ()
{
CGFloat selfW, selfH;
NSArray *source;
}
@property (nonatomic, strong) CAShapeLayer *xAxis;
@property (nonatomic, strong) CAShapeLayer *yAxis;
@property (nonatomic, strong) UIScrollView *lineScrollView;
@property (nonatomic, strong) CABasicAnimation *animation;
@end
@implementation SJLineChart
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
//
selfW = frame.size.width;
selfH = frame.size.height;
self.backgroundColor = [UIColor lightGrayColor];
}
return self;
}
- (void)showLineChart:(NSArray *)sourceArray {
source = sourceArray;
[self addxyAxis];
[self addSubview:self.lineScrollView];
_lineScrollView.contentSize = CGSizeMake(sourceArray.count*(space+1), 0);
[self drawLineChart:sourceArray];
[self drawPoint:sourceArray];
}
- (void)drawLineChart:(NSArray *)array {
CAShapeLayer *lineLayer = [CAShapeLayer layer];
lineLayer.fillColor = [UIColor clearColor].CGColor;
lineLayer.strokeColor = [UIColor redColor].CGColor;
lineLayer.lineWidth = 2.0;
//轨迹
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(space, _lineScrollView.frame.size.height - 60 - lineWidth/2.0 - ([[array objectAtIndex:0] floatValue] * scale))];
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
//
if (idx > 0) {
CGFloat y = _lineScrollView.frame.size.height-60 - lineWidth/2.0 - ([obj floatValue] * scale);
[path addLineToPoint:CGPointMake(space*(idx+1), y)];
}
}];
lineLayer.path = path.CGPath;
[self.lineScrollView.layer addSublayer:lineLayer];
[lineLayer addAnimation:self.animation forKey:@"lineStrokeEndAnimation"];
}
//把点标出来
- (void)drawPoint:(NSArray *)array {
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
//
CGFloat y = _lineScrollView.frame.size.height - 60 - lineWidth/2.0 - [obj floatValue]*scale;
UIBezierPath *circlePath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(space * (idx+1), y) radius:radius startAngle:0 endAngle:(M_PI)*2 clockwise:YES];
CAShapeLayer *circleLayer = [CAShapeLayer layer];
circleLayer.fillColor = [UIColor orangeColor].CGColor;
circleLayer.strokeColor = [UIColor clearColor].CGColor;
circleLayer.path = circlePath.CGPath;
[_lineScrollView.layer addSublayer:circleLayer];
}];
}
//添加坐标轴
- (void)addxyAxis {
self.xAxis = [self lineWithStartPoint:CGPointMake(distance, selfH-30) breakPoint:CGPointMake(kDeviceWidth-distance, selfH-30) endPoint:CGPointMake(kDeviceWidth-distance-cornerW, selfH-30-cornerW)];
self.yAxis = [self lineWithStartPoint:CGPointMake(distance+lineWidth/2.0, selfH-30) breakPoint:CGPointMake(distance, 30) endPoint:CGPointMake(distance+cornerW, 30+cornerW)];
[self.layer addSublayer:self.xAxis];
[self.layer addSublayer:self.yAxis];
}
//画坐标轴
- (CAShapeLayer *)lineWithStartPoint:(CGPoint)startPoint breakPoint:(CGPoint)breakPoint endPoint:(CGPoint)endPoint {
CAShapeLayer *line = [CAShapeLayer layer];
line.fillColor = [UIColor clearColor].CGColor;
line.strokeColor = [UIColor blackColor].CGColor;
line.lineWidth = 1.0;
UIBezierPath *linePath = [UIBezierPath bezierPath];
[linePath moveToPoint:startPoint];
[linePath addLineToPoint:breakPoint];
[linePath addLineToPoint:endPoint];
line.path = linePath.CGPath;
[line addAnimation:self.animation forKey:@"xyLineStrokeEndAnimation"];
return line;
}
- (UIScrollView *)lineScrollView {
if (_lineScrollView == nil) {
_lineScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(distance+lineWidth, 30, kDeviceWidth-distance*2-lineWidth, selfH-60-lineWidth/2.0)];
_lineScrollView.bounces = NO;
_lineScrollView.showsHorizontalScrollIndicator = NO;
}
return _lineScrollView;
}
- (CABasicAnimation *)animation {
if (_animation == nil) {
_animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
_animation.fromValue = @0.0;
_animation.toValue = @1.0;
_animation.duration = 2.0;
_animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
}
return _animation;
}
@end
结语
感谢阅读全文的朋友。
☞demo地址 https://github.com/SPIREJ/SJCAShapeLayer
相关阅读
上一篇:CAShapeLayer & UIBezierPath & CABasicAnimation