使用CAShapeLayer & UIBezierPath画一些形状

本篇主要从以下几个方面来写的一点东西:

  • 线段
  • 曲线
  • 动画
  • 简单的柱状图
  • 简单的折线图

线段

使用CAShapeLayer & UIBezierPath画一些形状_第1张图片
线段
  • 单线段
    两点确定一条直线,给贝塞尔曲线一个起始点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 & UIBezierPath画一些形状_第2张图片
    线端点样式示例

  • 线段拐点处样式
    CAShapeLayer的lineJoin属性决定线端点样式,可选样式kCALineJoinMiter(尖角)kCALineJoinRound(圆角)kCALineJoinBevel(平角)。默认为kCALineJoinMiter

    使用CAShapeLayer & UIBezierPath画一些形状_第3张图片
    拐角样式示例

  • 虚线

@property(nullable, copy) NSArray *lineDashPattern;

CAShapeLayer的lineDashPattern属性决定你画出一条什么样的虚线,这个属性返回一组NSNumber类型的数组,其实就是实虚相交来表示你的虚线,数组的长度由你决定(当然最好不要第一轮实虚相加超过线段长度)。比如line.lineDashPattern = @[@10,@5,@2,@8];就是表示每轮都为长度为10的实线,长度为5的虚线,长度为2的实线,长度为8的虚线,循环直到线段结束。

曲线

使用CAShapeLayer & UIBezierPath画一些形状_第4张图片
曲线
  • 二次贝塞尔曲线


    使用CAShapeLayer & UIBezierPath画一些形状_第5张图片
    二次贝塞尔曲线

    二次贝塞尔曲线有一个控制点,控制点的位置决定了显示一条怎样的曲线。下面的例子,我把起点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;
  • 三次贝塞尔曲线


    使用CAShapeLayer & UIBezierPath画一些形状_第6张图片
    三次贝塞尔曲线

    三次贝塞尔曲线有两个控制点,两个控制点的位置决定了显示一条怎样的曲线。下面的例子,我把起点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];
}
使用CAShapeLayer & UIBezierPath画一些形状_第7张图片
圆角矩形
  • 虚线圆
    如果是静态的(无动画),那么需要两个贝塞尔圆环曲线表示内圆和外圆,内圆一周,外圆实时进度。
    如果是动态的(有动画),那么可以一个贝塞尔圆环曲线表示内圆和外圆,内、外圆都一周,外圆添加动画,动画的toValue标志实时进度。
    使用CAShapeLayer & UIBezierPath画一些形状_第8张图片
    虚线圆
- (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;
}

动画

现在我们来给一些图形加上动画,使运行起来更美观。


使用CAShapeLayer & UIBezierPath画一些形状_第9张图片
动画
  • 主要写了三类动画
    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;
}

简单的柱状图

使用CAShapeLayer & UIBezierPath画一些形状_第10张图片
简单柱状图

这是个非常简单的柱状图,需要注意的是柱子的三个重要部分,起点、终点、柱宽。柱子由起点根据柱宽向左右两边扩张,如下图柱子的起点是位置2而不是位置1。


使用CAShapeLayer & UIBezierPath画一些形状_第11张图片
柱状图
#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

折线图

使用CAShapeLayer & UIBezierPath画一些形状_第12张图片
折线图

柱状图是一条条单独的线段,折线图就是一条连起来的完整折线。

#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

你可能感兴趣的:(使用CAShapeLayer & UIBezierPath画一些形状)