美文网首页IOS文章收集iOS DeveloperUi
使用CAShapeLayer & UIBezierPat

使用CAShapeLayer & UIBezierPat

作者: SPIREJ | 来源:发表于2017-02-27 16:36 被阅读218次

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

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

    线段

    线段
    • 单线段
      两点确定一条直线,给贝塞尔曲线一个起始点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<NSNumber *> *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

    相关文章

      本文标题:使用CAShapeLayer & UIBezierPat

      本文链接:https://www.haomeiwen.com/subject/suizwttx.html