Chr*_*ter 71 core-animation core-graphics ios
我一直试图找出一种绘制线段的方法,如下图所示:

我想:
我一直试图用CGContextAddArc类似的电话来做这件事,但没有走得太远.
有人可以帮忙吗?
Dav*_*ist 180
你的问题有很多部分.
为这样的段创建路径不应该太难.有两个弧线和两条直线.我之前已经解释过如何打破这样的道路,所以我不会在这里做.相反,我会想要通过抚摸另一条路径来创造路径.您当然可以阅读细分并自己构建路径.我正在谈论的弧线是灰色虚线结束内的橙色弧线.

为了抚摸我们首先需要它的路径.这基本上就像移动到起点并在中心周围绘制一条弧线一样简单,从当前角度到您希望线段覆盖的角度.
CGMutablePathRef arc = CGPathCreateMutable();
CGPathMoveToPoint(arc, NULL,
startPoint.x, startPoint.y);
CGPathAddArc(arc, NULL,
centerPoint.x, centerPoint.y,
radius,
startAngle,
endAngle,
YES);
Run Code Online (Sandbox Code Playgroud)
然后,当您拥有该路径(单个圆弧)时,您可以通过使用特定宽度进行描边来创建新分段.得到的路径将具有两条直线和两条弧.中风从中心向内和向外发生相等的距离.
CGFloat lineWidth = 10.0;
CGPathRef strokedArc =
CGPathCreateCopyByStrokingPath(arc, NULL,
lineWidth,
kCGLineCapButt,
kCGLineJoinMiter, // the default
10); // 10 is default miter limit
Run Code Online (Sandbox Code Playgroud)
接下来是绘图,通常有两个主要选择:Core Graphics in drawRect:或带有Core Animation的shape层.Core Graphics将为您提供更强大的绘图,但Core Animation将为您提供更好的动画性能.由于路径涉及纯Cora动画将无法工作.你最终会得到奇怪的文物.但是,我们可以通过绘制图层的图形上下文来使用图层和核心图形的组合.
我们已经有了基本的形状,但在我们添加渐变和阴影之前,我会做一个基本的填充和描边(你的图像中有黑色描边).
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextAddPath(c, strokedArc);
CGContextSetFillColorWithColor(c, [UIColor lightGrayColor].CGColor);
CGContextSetStrokeColorWithColor(c, [UIColor blackColor].CGColor);
CGContextDrawPath(c, kCGPathFillStroke);
Run Code Online (Sandbox Code Playgroud)
这将在屏幕上显示这样的东西

我要改变顺序并在渐变之前做阴影.要绘制阴影,我们需要为上下文配置阴影,并绘制填充形状以使用阴影绘制阴影.然后我们需要恢复上下文(在阴影之前)并再次描绘形状.
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);
Run Code Online (Sandbox Code Playgroud)
此时结果是这样的

对于渐变,我们需要一个渐变层.我在这里做了一个非常简单的双色渐变,但您可以根据需要自定义它.要创建渐变,我们需要获得颜色和合适的颜色空间.然后我们可以在填充顶部绘制渐变(但在笔划之前).我们还需要将渐变掩盖到与以前相同的路径.为此,我们剪辑路径.
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);
Run Code Online (Sandbox Code Playgroud)
这完成了绘图,因为我们目前有这个结果

当涉及到形状的动画时,它已经全部写过:使用自定义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];
Run Code Online (Sandbox Code Playgroud)
该图层具有一些与阴影相关的属性,由您自定义.但是,您应该设置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
Run Code Online (Sandbox Code Playgroud)
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);
Run Code Online (Sandbox Code Playgroud)
如果我们现在绘制渐变,它将在形状的顶部而不是在其内部.不,我们不能对形状进行渐变填充(我知道你在考虑它).我们需要屏蔽渐变,使其超出细分.为此,我们创建另一个层作为该段的掩码.它必须是另一层,如果掩码是层次结构的一部分,则文档清楚表明行为是"未定义的".由于蒙版的坐标系将与渐变子层的坐标系相同,因此我们必须在设置之前平移线段形状.
CAShapeLayer *mask = [CAShapeLayer layer];
CGAffineTransform translation = CGAffineTransformMakeTranslation(-CGRectGetMinX(gradient.frame),
-CGRectGetMinY(gradient.frame));
mask.path = CGPathCreateCopyByTransformingPath(segment.path,
&translation);
gradient.mask = mask;
Run Code Online (Sandbox Code Playgroud)
rob*_*off 39
Quartz 2D Programming Guide中涵盖了您需要的一切.我建议你仔细看看.
但是,将它们放在一起可能很困难,所以我将引导您完成它.我们将编写一个函数,它接受一个大小并返回一个看起来大致像你的一个段的图像:

我们像这样启动函数定义:
static UIImage *imageWithSize(CGSize size) {
Run Code Online (Sandbox Code Playgroud)
我们需要一个段的厚度常数:
static CGFloat const kThickness = 20;
Run Code Online (Sandbox Code Playgroud)
和一个用于概述段的线宽的常量:
static CGFloat const kLineWidth = 1;
Run Code Online (Sandbox Code Playgroud)
和阴影大小的常量:
static CGFloat const kShadowWidth = 8;
Run Code Online (Sandbox Code Playgroud)
接下来我们需要创建一个图像上下文来绘制:
UIGraphicsBeginImageContextWithOptions(size, NO, 0); {
Run Code Online (Sandbox Code Playgroud)
我在那条线的末端放了一个左括号,因为我喜欢额外的压痕水平,以提醒我UIGraphicsEndImageContext稍后再打电话.
由于我们需要调用的很多函数是Core Graphics(也就是Quartz 2D)函数,而不是UIKit函数,我们需要得到CGContext:
CGContextRef gc = UIGraphicsGetCurrentContext();
Run Code Online (Sandbox Code Playgroud)
现在我们已经准备好开始了.首先,我们在路径中添加一个弧.弧线沿着我们想要绘制的线段的中心运行:
CGContextAddArc(gc, size.width / 2, size.height / 2,
(size.width - kThickness - kLineWidth) / 2,
-M_PI / 4, -3 * M_PI / 4, YES);
Run Code Online (Sandbox Code Playgroud)
现在我们要求Core Graphics用一个描述路径的"描边"版本替换路径.我们首先将笔划的粗细设置为我们希望细分具有的厚度:
CGContextSetLineWidth(gc, kThickness);
Run Code Online (Sandbox Code Playgroud)
CGContextSetLineCap(gc, kCGLineCapButt);
Run Code Online (Sandbox Code Playgroud)
然后我们可以要求Core Graphics用一个描边版本替换路径:
CGContextReplacePathWithStrokedPath(gc);
Run Code Online (Sandbox Code Playgroud)
要使用线性渐变填充此路径,我们必须告诉Core Graphics将所有操作剪切到路径内部.这样做会使Core Graphics重置路径,但我们稍后需要路径来绘制边缘周围的黑线.所以我们将复制路径:
CGPathRef path = CGContextCopyPath(gc);
Run Code Online (Sandbox Code Playgroud)
由于我们希望片段投射阴影,我们将在进行任何绘制之前设置阴影参数:
CGContextSetShadowWithColor(gc,
CGSizeMake(0, kShadowWidth / 2), kShadowWidth / 2,
[UIColor colorWithWhite:0 alpha:0.3].CGColor);
Run Code Online (Sandbox Code Playgroud)
我们将填充段(使用渐变)并描绘它(绘制黑色轮廓).我们想要两个操作都有一个阴影.我们通过开始透明层来告诉Core Graphics:
CGContextBeginTransparencyLayer(gc, 0); {
Run Code Online (Sandbox Code Playgroud)
我在那条线的末端放了一个左括号,因为我喜欢额外的缩进程度,以提醒我CGContextEndTransparencyLayer稍后再打电话.
由于我们要更改上下文的剪辑区域以进行填充,但是我们不想在稍后描边时剪切,我们需要保存图形状态:
CGContextSaveGState(gc); {
Run Code Online (Sandbox Code Playgroud)
我在那条线的末端放了一个左括号,因为我喜欢额外的缩进程度,以提醒我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);
Run Code Online (Sandbox Code Playgroud)
我们还需要找出渐变的起点和终点.我们将使用路径边界框:
CGRect bbox = CGContextGetPathBoundingBox(gc);
CGPoint start = bbox.origin;
CGPoint end = CGPointMake(CGRectGetMaxX(bbox), CGRectGetMaxY(bbox));
Run Code Online (Sandbox Code Playgroud)
我们将强制水平或垂直绘制渐变,以较长者为准:
if (bbox.size.width > bbox.size.height) {
end.y = start.y;
} else {
end.x = start.x;
}
Run Code Online (Sandbox Code Playgroud)
现在我们终于拥有绘制渐变所需的一切.首先我们剪辑到路径:
CGContextClip(gc);
Run Code Online (Sandbox Code Playgroud)
然后我们绘制渐变:
CGContextDrawLinearGradient(gc, gradient, start, end, 0);
Run Code Online (Sandbox Code Playgroud)
然后我们可以释放渐变并恢复保存的图形状态:
CGGradientRelease(gradient);
} CGContextRestoreGState(gc);
Run Code Online (Sandbox Code Playgroud)
当我们调用时CGContextClip,Core Graphics重置上下文的路径.该路径不是已保存图形状态的一部分; 这就是我们之前制作副本的原因.现在是时候使用该副本再次在上下文中设置路径:
CGContextAddPath(gc, path);
CGPathRelease(path);
Run Code Online (Sandbox Code Playgroud)
现在我们可以描绘路径,绘制段的黑色轮廓:
CGContextSetLineWidth(gc, kLineWidth);
CGContextSetLineJoin(gc, kCGLineJoinMiter);
[[UIColor blackColor] setStroke];
CGContextStrokePath(gc);
Run Code Online (Sandbox Code Playgroud)
接下来我们告诉Core Graphics结束透明层.这将使它看到我们绘制的内容并在下面添加阴影:
} CGContextEndTransparencyLayer(gc);
Run Code Online (Sandbox Code Playgroud)
现在我们都完成了绘图.我们要求UIKit UIImage从图像上下文创建一个,然后销毁上下文并返回图像:
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
Run Code Online (Sandbox Code Playgroud)
你可以在这个要点中找到所有代码.
这是Rob Mayoff 答案的Swift 3版本。看看这种语言的效率有多高!这可能是 MView.swift 文件的内容:
\n\nimport UIKit\n\nclass MView: UIView {\n\n var size = CGSize.zero\n\n override init(frame: CGRect) {\n super.init(frame: frame)\n size = frame.size\n }\n\n required init?(coder aDecoder: NSCoder) {\n fatalError("init(coder:) has not been implemented")\n }\n\n var niceImage: UIImage {\n\n let kThickness = CGFloat(20)\n let kLineWidth = CGFloat(1)\n let kShadowWidth = CGFloat(8)\n\n UIGraphicsBeginImageContextWithOptions(size, false, 0)\n\n let gc = UIGraphicsGetCurrentContext()!\n gc.addArc(center: CGPoint(x: size.width/2, y: size.height/2),\n radius: (size.width - kThickness - kLineWidth)/2,\n startAngle: -45\xc2\xb0,\n endAngle: -135\xc2\xb0,\n clockwise: true)\n\n gc.setLineWidth(kThickness)\n gc.setLineCap(.butt)\n gc.replacePathWithStrokedPath()\n\n let path = gc.path!\n\n gc.setShadow(\n offset: CGSize(width: 0, height: kShadowWidth/2),\n blur: kShadowWidth/2,\n color: UIColor.gray.cgColor\n )\n\n gc.beginTransparencyLayer(auxiliaryInfo: nil)\n\n gc.saveGState()\n\n let rgb = CGColorSpaceCreateDeviceRGB()\n\n let gradient = CGGradient(\n colorsSpace: rgb,\n colors: [UIColor.gray.cgColor, UIColor.white.cgColor] as CFArray,\n locations: [CGFloat(0), CGFloat(1)])!\n\n let bbox = path.boundingBox\n let startP = bbox.origin\n var endP = CGPoint(x: bbox.maxX, y: bbox.maxY);\n if (bbox.size.width > bbox.size.height) {\n endP.y = startP.y\n } else {\n endP.x = startP.x\n }\n\n gc.clip()\n\n gc.drawLinearGradient(gradient, start: startP, end: endP,\n options: CGGradientDrawingOptions(rawValue: 0))\n\n gc.restoreGState()\n\n gc.addPath(path)\n\n gc.setLineWidth(kLineWidth)\n gc.setLineJoin(.miter)\n UIColor.black.setStroke()\n gc.strokePath()\n\n gc.endTransparencyLayer()\n\n\n let image = UIGraphicsGetImageFromCurrentImageContext()!\n UIGraphicsEndImageContext()\n return image\n }\n\n override func draw(_ rect: CGRect) {\n niceImage.draw(at:.zero)\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n\n从 viewController 中调用它,如下所示:
\n\nlet vi = MView(frame: self.view.bounds)\nself.view.addSubview(vi)\nRun Code Online (Sandbox Code Playgroud)\n\n为了将度数转换为弧度,我创建了\xc2\xb0后缀运算符。因此,您现在可以使用例如45\xc2\xb0,这可以将 45 度转换为弧度。\n此示例适用于 Ints,如果您有需要,也可以将其扩展为 Float 类型:
\n\npostfix operator \xc2\xb0\n\nprotocol IntegerInitializable: ExpressibleByIntegerLiteral {\n init (_: Int)\n}\n\nextension Int: IntegerInitializable {\n postfix public static func \xc2\xb0(lhs: Int) -> CGFloat {\n return CGFloat(lhs) * .pi / 180\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n\n将此代码放入实用程序 swift 文件中。
\n| 归档时间: |
|
| 查看次数: |
30949 次 |
| 最近记录: |