绘制从一个圆或圆环段(Draw segments from a circle or donut)

2019-08-20 04:05发布

我一直在试图找出一种方法来绘制段如下面所示的图像:

我想:

  1. 绘制线段
  2. 包括梯度
  3. 包括阴影
  4. 动画从0到n的角度绘制

我一直在试图与要做到这一点CGContextAddArc和类似的电话,但没有得到很远。

任何人都可以帮忙吗?

Answer 1:

有很多地方你的问题。

获取路径

创建这样一个细分的路径应该不会太难。 有两个圆弧和两条直线。 我前面解释了如何打破这样的路径 ,所以我不会做它在这里。 但我会被看中并抚摸另一个路径创建路径。 当然,你可以阅读击穿并构建自己的路径。 我说的是轻抚弧是灰色短落脚点里面的橙色弧线。

要描边路径,我们首先需要它。 这基本上是从当前的角度你想要的段覆盖的角度移动到起点和绘制围绕中心圆弧一样简单。

CGMutablePathRef arc = CGPathCreateMutable();
CGPathMoveToPoint(arc, NULL,
                  startPoint.x, startPoint.y);
CGPathAddArc(arc, NULL,
             centerPoint.x, centerPoint.y,
             radius,
             startAngle,
             endAngle,
             YES);

然后,当你有一个路径(单弧),你可以通过一定宽度抚摸着它创造新的细分市场。 所得到的路径都将有两条直线和两条弧。 行程从中心的距离相等向内和向外发生。

CGFloat lineWidth = 10.0;
CGPathRef strokedArc =
    CGPathCreateCopyByStrokingPath(arc, NULL,
                                   lineWidth,
                                   kCGLineCapButt,
                                   kCGLineJoinMiter, // the default
                                   10); // 10 is default miter limit

画画

接下来是绘制,并且通常主要有两种选择:核心图形中drawRect:或与核心动画形状的层。 核芯显卡将会给您更强大的绘图,但核心动画是想给你更好的动画性能。 由于路径涉及纯科拉动画将无法正常工作。 最后你会用异样的文物。 我们但是,可以通过绘制层的图形上下文中使用层和核心图形的组合。

填充和描边段

我们已经有了基本的形状,但之前我们添加渐变和阴影吧,我会做一个基本的填充和中风(你有你的形象黑色中风)。

CGContextRef c = UIGraphicsGetCurrentContext();
CGContextAddPath(c, strokedArc);
CGContextSetFillColorWithColor(c, [UIColor lightGrayColor].CGColor);
CGContextSetStrokeColorWithColor(c, [UIColor blackColor].CGColor);
CGContextDrawPath(c, kCGPathFillStroke);

这将会把这样的事情在屏幕上

添加阴影

我要改变顺序和梯度之前做的影子。 要绘制阴影,我们需要配置上下文中的阴影和绘制填充形状与阴影绘制。 然后,我们需要重新恢复的背景下(之前的阴影)和中风的形状。

CGColorRef shadowColor = [UIColor colorWithWhite:0.0 alpha:0.75].CGColor;
CGContextSaveGState(c);
CGContextSetShadowWithColor(c,
                            CGSizeMake(0, 2), // Offset
                            3.0,              // Radius
                            shadowColor);
CGContextFillPath(c);
CGContextRestoreGState(c);

// Note that filling the path "consumes it" so we add it again
CGContextAddPath(c, strokedArc);
CGContextStrokePath(c);

此时的结果是这样的

绘制渐变

渐变,我们需要一个渐变层。 我在这里做一个非常简单的两色渐变,但你可以定制你想要的。 要创建渐变,我们需要得到的颜色和合适的色彩空间。 然后,我们可以借鉴的填充(但中风前)顶部的梯度。 我们也需要梯度掩蔽,以相同的路径之前。 要做到这一点,我们夹的路径。

CGFloat colors [] = {
    0.75, 1.0, // light gray   (fully opaque)
    0.90, 1.0  // lighter gray (fully opaque)
};

CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceGray(); // gray colors want gray color space
CGGradientRef gradient = CGGradientCreateWithColorComponents(baseSpace, colors, NULL, 2);
CGColorSpaceRelease(baseSpace), baseSpace = NULL;

CGContextSaveGState(c);
CGContextAddPath(c, strokedArc);
CGContextClip(c);

CGRect boundingBox = CGPathGetBoundingBox(strokedArc);
CGPoint gradientStart = CGPointMake(0, CGRectGetMinY(boundingBox));
CGPoint gradientEnd   = CGPointMake(0, CGRectGetMaxY(boundingBox));

CGContextDrawLinearGradient(c, gradient, gradientStart, gradientEnd, 0);
CGGradientRelease(gradient), gradient = NULL;
CGContextRestoreGState(c);

这就完成了图纸,因为我们现在有这样的结果

动画

当谈到形状的动画它的动画饼图切片使用自定义的CALayer:所有以前写 。 如果您尝试通过简单的动画,你会看到动画过程中路径的一些非常时髦的扭曲的路径属性做图。 阴影和梯度已被原封不动为下面的图像中说明的目的。

我建议你吃,我已经张贴在这个答案的绘制代码,并通过从该文章的动画代码。 然后,你应该结束与你所要求的东西。


供参考:使用核心动画相同的图

平面形状

CAShapeLayer *segment = [CAShapeLayer layer];
segment.fillColor = [UIColor lightGrayColor].CGColor;
segment.strokeColor = [UIColor blackColor].CGColor;
segment.lineWidth = 1.0;
segment.path = strokedArc;

[self.view.layer addSublayer:segment];

添加阴影

该层有一些阴影相关的属性,它是由你来定制。 Howerever你应该设置shadowPath以提高性能特性。

segment.shadowColor = [UIColor blackColor].CGColor;
segment.shadowOffset = CGSizeMake(0, 2);
segment.shadowOpacity = 0.75;
segment.shadowRadius = 3.0;
segment.shadowPath = segment.path; // Important for performance

绘制渐变

CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.colors = @[(id)[UIColor colorWithWhite:0.75 alpha:1.0].CGColor,  // light gray
                    (id)[UIColor colorWithWhite:0.90 alpha:1.0].CGColor]; // lighter gray
gradient.frame = CGPathGetBoundingBox(segment.path);

如果我们提请梯度现在将是对形状的顶部,而不是在它里面。 不,我们不能有形状(我知道你在想的话)的渐变填充。 我们需要屏蔽梯度,使得它走外段。 要做到这一点,我们创建另一个层是该段的面具。 它必须是另一层的文档很清楚,行为是“未定义”如果掩码是层层次结构的一部分。 由于面膜的坐标系将是相同的子层的渐变,我们将设置之前,该段形状翻译。

CAShapeLayer *mask = [CAShapeLayer layer];
CGAffineTransform translation = CGAffineTransformMakeTranslation(-CGRectGetMinX(gradient.frame),
                                                                 -CGRectGetMinY(gradient.frame));
mask.path = CGPathCreateCopyByTransformingPath(segment.path,
                                               &translation);
gradient.mask = mask;


Answer 2:

你需要的一切是覆盖在石英2D编程指南 。 我建议你通过它看。

但是,它可能很难把它放在一起,所以我将带您穿过。 我们将编写一个函数,它的尺寸和返回,看起来大致是你的细分市场之一的图像:

我们先从函数定义是这样的:

static UIImage *imageWithSize(CGSize size) {

我们需要一个常数段的厚度:

    static CGFloat const kThickness = 20;

和恒定的线概述段的宽度:

    static CGFloat const kLineWidth = 1;

和恒定的阴影的大小:

    static CGFloat const kShadowWidth = 8;

接下来,我们需要创建一个用于绘制图像内容:

    UIGraphicsBeginImageContextWithOptions(size, NO, 0); {

我把左大括号在该行的结束,因为我喜欢缩进一层额外的提醒我要打电话UIGraphicsEndImageContext以后。

因为很多时候我们需要调用函数是核芯显卡(又名石英2D)的功能,而不是UIKit的功能,我们需要得到CGContext

        CGContextRef gc = UIGraphicsGetCurrentContext();

现在,我们已经准备好真正开始。 首先,我们的弧添加到路径。 电弧沿着我们要绘制线段的中心运行:

        CGContextAddArc(gc, size.width / 2, size.height / 2,
            (size.width - kThickness - kLineWidth) / 2,
            -M_PI / 4, -3 * M_PI / 4, YES);

现在我们要问的核芯显卡,以取代与勾勒出路径的“抚摸”版本的路径。 我们首先设置了笔画,我们希望段有厚度的厚度:

        CGContextSetLineWidth(gc, kThickness);

和我们设定的线帽样式“对接”,所以我们不得不方形端 :

        CGContextSetLineCap(gc, kCGLineCapButt);

然后,我们可以问核芯显卡与描边版本替换路径:

        CGContextReplacePathWithStrokedPath(gc);

为了填补这一路径以线性渐变,我们要告诉核芯显卡夹的所有操作路径的内部。 这样做将核芯显卡重置路径,但后来我们需要的路径周围绘制边缘的黑线。 因此,我们将在这里复制路径:

        CGPathRef path = CGContextCopyPath(gc);

由于我们希望段蒙上了一层阴影,我们将设置阴影参数,我们做任何绘图之前:

        CGContextSetShadowWithColor(gc,
            CGSizeMake(0, kShadowWidth / 2), kShadowWidth / 2,
            [UIColor colorWithWhite:0 alpha:0.3].CGColor);

我们要既填补段(具有梯度)和中风它(绘制黑色轮廓)。 我们希望您在操作一个影子。 我们告诉核芯显卡,通过开始透明层:

        CGContextBeginTransparencyLayer(gc, 0); {

我把左大括号在该行的结束,因为我喜欢有压痕一层额外的提醒我要打电话CGContextEndTransparencyLayer以后。

因为我们要改变上下文的剪辑区域填充,但我们不会想剪辑时我们行程的轮廓后,我们需要保存图形状态:

            CGContextSaveGState(gc); {

我把左大括号在该行的结束,因为我喜欢有压痕一层额外的提醒我要打电话CGContextRestoreGState以后。

要使用渐变填充路径,我们需要创建一个渐变的对象:

                CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
                CGGradientRef gradient = CGGradientCreateWithColors(rgb, (__bridge CFArrayRef)@[
                    (__bridge id)[UIColor grayColor].CGColor,
                    (__bridge id)[UIColor whiteColor].CGColor
                ], (CGFloat[]){ 0.0f, 1.0f });
                CGColorSpaceRelease(rgb);

我们还需要搞清楚一个起点和终点的梯度。 我们将使用路径边框:

                CGRect bbox = CGContextGetPathBoundingBox(gc);
                CGPoint start = bbox.origin;
                CGPoint end = CGPointMake(CGRectGetMaxX(bbox), CGRectGetMaxY(bbox));

我们将迫使梯度进行水平或垂直绘制,以较长者为准:

                if (bbox.size.width > bbox.size.height) {
                    end.y = start.y;
                } else {
                    end.x = start.x;
                }

现在,我们终于有我们需要绘制渐变的一切。 首先,我们要夹的路径:

                CGContextClip(gc);

然后,我们得出的梯度:

                CGContextDrawLinearGradient(gc, gradient, start, end, 0);

然后我们就可以释放出梯度和恢复保存的图形状态:

                CGGradientRelease(gradient);
            } CGContextRestoreGState(gc);

当我们叫CGContextClip ,核芯显卡重置上下文的路径。 路径是不保存的图形状态的一部分; 这就是为什么我们的副本早些时候。 现在是时候使用该副本重新设定在上下文路径:

            CGContextAddPath(gc, path);
            CGPathRelease(path);

现在,我们可以描边路径,绘制段的黑色轮廓:

            CGContextSetLineWidth(gc, kLineWidth);
            CGContextSetLineJoin(gc, kCGLineJoinMiter);
            [[UIColor blackColor] setStroke];
            CGContextStrokePath(gc);

接下来,我们告诉核芯显卡结束透明层。 这将使它看看我们已经开什么,下面添加阴影:

        } CGContextEndTransparencyLayer(gc);

现在,我们就大功告成了画画。 我们要求的UIKit创建UIImage从图像内容,然后销毁上下文并返回图像:

    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

你可以找到的代码一起在这个要点 。



Answer 3:

这是斯威夫特3版本罗布Mayoff的回答。 刚看到这个语言是如何更有效的是! 这可能是一个MView.swift文件的内容:

import UIKit

class MView: UIView {

    var size = CGSize.zero

    override init(frame: CGRect) {
    super.init(frame: frame)
    size = frame.size
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    var niceImage: UIImage {

        let kThickness = CGFloat(20)
        let kLineWidth = CGFloat(1)
        let kShadowWidth = CGFloat(8)

        UIGraphicsBeginImageContextWithOptions(size, false, 0)

            let gc = UIGraphicsGetCurrentContext()!
            gc.addArc(center: CGPoint(x: size.width/2, y: size.height/2),
                   radius: (size.width - kThickness - kLineWidth)/2,
                   startAngle: -45°,
                   endAngle: -135°,
                   clockwise: true)

            gc.setLineWidth(kThickness)
            gc.setLineCap(.butt)
            gc.replacePathWithStrokedPath()

            let path = gc.path!

            gc.setShadow(
                offset: CGSize(width: 0, height: kShadowWidth/2),
                blur: kShadowWidth/2,
                color: UIColor.gray.cgColor
            )

            gc.beginTransparencyLayer(auxiliaryInfo: nil)

                gc.saveGState()

                    let rgb = CGColorSpaceCreateDeviceRGB()

                    let gradient = CGGradient(
                        colorsSpace: rgb,
                        colors: [UIColor.gray.cgColor, UIColor.white.cgColor] as CFArray,
                        locations: [CGFloat(0), CGFloat(1)])!

                    let bbox = path.boundingBox
                    let startP = bbox.origin
                    var endP = CGPoint(x: bbox.maxX, y: bbox.maxY);
                    if (bbox.size.width > bbox.size.height) {
                        endP.y = startP.y
                    } else {
                        endP.x = startP.x
                    }

                    gc.clip()

                    gc.drawLinearGradient(gradient, start: startP, end: endP,
                                          options: CGGradientDrawingOptions(rawValue: 0))

                gc.restoreGState()

                gc.addPath(path)

                gc.setLineWidth(kLineWidth)
                gc.setLineJoin(.miter)
                UIColor.black.setStroke()
                gc.strokePath()

            gc.endTransparencyLayer()


        let image = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return image
    }

    override func draw(_ rect: CGRect) {
        niceImage.draw(at:.zero)
    }
}

从的viewController这样称呼它:

let vi = MView(frame: self.view.bounds)
self.view.addSubview(vi)

要做到度弧度的转换我创建了°后缀运算符。 所以,你现在可以使用如45°,这样做的转化率从45度到弧度。 这个例子是整型,也扩展了这些浮子类型,如果你有需要:

postfix operator °

protocol IntegerInitializable: ExpressibleByIntegerLiteral {
  init (_: Int)
}

extension Int: IntegerInitializable {
  postfix public static func °(lhs: Int) -> CGFloat {
    return CGFloat(lhs) * .pi / 180
  }
}

将这个代码到一个事业迅速文件。



文章来源: Draw segments from a circle or donut