美文网首页iOS 细节大集合iOS 动画贝塞尔曲线和layer设置图像
iOS 画贝塞尔曲线 连续曲线 平滑曲线 曲线图表

iOS 画贝塞尔曲线 连续曲线 平滑曲线 曲线图表

作者: ElegantLiar | 来源:发表于2017-06-22 16:48 被阅读2507次

    利用贝塞尔曲线画一段连续曲线

    bezierDemo2.png bezierDemo1.png image.png

    如果我们根据几个点画一条连续的曲线, 我们使用的方法是

    - (void)addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2;
    

    这个方法是由一个结束点和两个控制点作为参数, 画上一个点到这个结束点之间的一段曲线

    首先求出控制点是画各个曲线的必须条件, 下面这个方法是求得控制点的方法, 需要我们传入4个点, 求出控制点1和控制点2

    - (void)getControlPointx0:(CGFloat)x0 andy0:(CGFloat)y0
                           x1:(CGFloat)x1 andy1:(CGFloat)y1
                           x2:(CGFloat)x2 andy2:(CGFloat)y2
                           x3:(CGFloat)x3 andy3:(CGFloat)y3
                         path:(UIBezierPath*) path{
        CGFloat smooth_value =0.6;
        CGFloat ctrl1_x;
        CGFloat ctrl1_y;
        CGFloat ctrl2_x;
        CGFloat ctrl2_y;
        CGFloat xc1 = (x0 + x1) /2.0;
        CGFloat yc1 = (y0 + y1) /2.0;
        CGFloat xc2 = (x1 + x2) /2.0;
        CGFloat yc2 = (y1 + y2) /2.0;
        CGFloat xc3 = (x2 + x3) /2.0;
        CGFloat yc3 = (y2 + y3) /2.0;
        CGFloat len1 = sqrt((x1-x0) * (x1-x0) + (y1-y0) * (y1-y0));
        CGFloat len2 = sqrt((x2-x1) * (x2-x1) + (y2-y1) * (y2-y1));
        CGFloat len3 = sqrt((x3-x2) * (x3-x2) + (y3-y2) * (y3-y2));
        CGFloat k1 = len1 / (len1 + len2);
        CGFloat k2 = len2 / (len2 + len3);
        CGFloat xm1 = xc1 + (xc2 - xc1) * k1;
        CGFloat ym1 = yc1 + (yc2 - yc1) * k1;
        CGFloat xm2 = xc2 + (xc3 - xc2) * k2;
        CGFloat ym2 = yc2 + (yc3 - yc2) * k2;
        ctrl1_x = xm1 + (xc2 - xm1) * smooth_value + x1 - xm1;
        ctrl1_y = ym1 + (yc2 - ym1) * smooth_value + y1 - ym1;
        ctrl2_x = xm2 + (xc2 - xm2) * smooth_value + x2 - xm2;
        ctrl2_y = ym2 + (yc2 - ym2) * smooth_value + y2 - ym2;
        [path addCurveToPoint:CGPointMake(x2, y2) controlPoint1:CGPointMake(ctrl1_x, ctrl1_y) controlPoint2:CGPointMake(ctrl2_x, ctrl2_y)];
    }
    

    比如我们要画出周二到周三之间的曲线, 我们需要传入周一, 周二, 周三, 周四, 4个点的坐标, 求得周二, 周三之间的两个控制点, 然后画出周二, 周三之间的曲线, 以此类推, 画出每一段曲线

    但是会发现, 只有周一到周日7个点是不够画出6段曲线的, 所以我们需要在首尾各添加一个点(firstPoint, endPoint如下图所示)来求出周一周二间的曲线和周六周日间的曲线

    image.png

    下面结合代码进行说明

    1. 找出周一到周日对应点的x, y坐标
    //初始化一个随机y的level
            _pointYArray = @[@(1), @(4), @(1), @(5), @(2), @(3), @(5)];
    
    //确定在view中的x, y准确的frame值
    [_pointYArray enumerateObjectsUsingBlock:^(NSNumber * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            NSInteger objInter = 1;
            if ([obj respondsToSelector:@selector(integerValue)]) {
                objInter = [obj integerValue];
            }
            if ([obj respondsToSelector:@selector(integerValue)]) {
                objInter = [obj integerValue];
                if (objInter < 1) {
                    objInter = 1;
                } else if (objInter > 5) {
                    objInter = 5;
                }
            }
            CGPoint point = CGPointMake(_xAxisSpacing * idx + _axisToViewPadding + 30, CGRectGetHeight(self.frame) - _axisToViewPadding - (objInter - 1) * _yAxisSpacing - 11);
            NSValue *value = [NSValue valueWithCGPoint:CGPointMake(point.x, point.y)];
            [_pointsArray addObject:value];
        }];
    

    2.添加首尾两个点, 用于求出周一到周二和周六到周日之间控制点的坐标点

    NSValue *firstPointValue = [NSValue valueWithCGPoint:CGPointMake(_axisToViewPadding, (CGRectGetHeight(self.frame) - _axisToViewPadding) / 2)];
    [_pointsArray insertObject:firstPointValue atIndex:0];
    NSValue *endPointValue = [NSValue valueWithCGPoint:CGPointMake(CGRectGetWidth(self.frame), (CGRectGetHeight(self.frame) - _axisToViewPadding) / 2)];
    [_pointsArray addObject:endPointValue];
    

    3.画曲线, 先将起点移动到周一坐标处, 根据firstPoint, 周一, 周二, 周三坐标画出周一到周二的曲线, 然后循环画出每一段曲线

    /** 折线路径 */
        UIBezierPath *path = [UIBezierPath bezierPath];
        for (NSInteger i = 0; i < 6; i++) {
            CGPoint p1 = [[_pointsArray objectAtIndex:i] CGPointValue];
            CGPoint p2 = [[_pointsArray objectAtIndex:i+1] CGPointValue];
            CGPoint p3 = [[_pointsArray objectAtIndex:i+2] CGPointValue];
            CGPoint p4 = [[_pointsArray objectAtIndex:i+3] CGPointValue];
            if (i == 0) {
                [path moveToPoint:p2];
            }
            [self getControlPointx0:p1.x andy0:p1.y x1:p2.x andy1:p2.y x2:p3.x andy2:p3.y x3:p4.x andy3:p4.y path:path];
        }
    

    4.添加CAShapeLayer显示path

    /** 将折线添加到折线图层上,并设置相关的属性 */
        _bezierLineLayer = [CAShapeLayer layer];
        _bezierLineLayer.path = path.CGPath;
        _bezierLineLayer.strokeColor = [UIColor redColor].CGColor;
        _bezierLineLayer.fillColor = [[UIColor clearColor] CGColor];
        // 默认设置路径宽度为0,使其在起始状态下不显示
        _bezierLineLayer.lineWidth = 3;
        _bezierLineLayer.lineCap = kCALineCapRound;
        _bezierLineLayer.lineJoin = kCALineJoinRound;
        [self.layer addSublayer:_bezierLineLayer];
    

    总结

    画连续曲线要用到这个三次贝塞尔曲线的方法, 其他的画曲线的方法画出的均是不平滑的, 而画曲线要求我们得出每一段之间控制曲线的两个控制点, 由四个点确定控制点的坐标, 求控制点方法中的smooth_value =0.6, 是用来控制曲线锐度的, 网上大多数提供的数值为0.6

    完整代码demo地址

    相关文章

      网友评论

      • 遥想月下:这个计算平滑的方法厉害了
      • 念念不忘一个丫头的容:您好,您画线出现过锯齿状态吗??怎么消除贝塞尔曲线的锯齿状,我用的是二阶贝塞尔曲线画的
        ElegantLiar:@念念不忘一个丫头的容 这个没有遇到 我帮你查下 如果查到相关资料 我再回复你
      • 英俊神武:楼主,这个demo展示出来点的坐标,为什么跟项目打印的点坐标不一样呢,能否解答一下
        英俊神武:@ElegantLiar 我打印了一下
        ElegantLiar:我这个demo里貌似没有打印对应的点吧
      • 不知道为什么:谢谢 找了好久

      本文标题:iOS 画贝塞尔曲线 连续曲线 平滑曲线 曲线图表

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