从圆圈或圆环画出片段 [英] Draw segments from a circle or donut

查看:79
本文介绍了从圆圈或圆环画出片段的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直试图想出一种绘制线段的方法,如下图所示:





我想:


  1. 绘制细分

  2. 包含渐变

  3. 包含阴影

  4. 将绘图从0设置为n角度

我一直在尝试使用 CGContextAddArc执行此操作和类似的电话但没有走得太远。



有人可以帮忙吗?

解决方案

你的问题有很多部分。



获取路径



为这样的段创建路径不应该太难。有两个弧和两条直线。我。如果您尝试通过简单地设置路径属性的动画来进行绘制,那么您将在动画期间看到一些非常时髦的路径变形。为了说明的目的,阴影和渐变保持不变,如下图所示。





我建议您使用我在此答案中发布的绘图代码并将其应用于动画代码那篇文章。然后你应该得到你想要的东西。






供参考:使用Core Animation的相同图纸



平原形状



  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; //表现很重要



绘制渐变



  CAGradientLayer * gradient = [CAGradientLayer layer]; 
gradient.colors = @ [(id)[UIColor colorWithWhite:0.75 alpha:1.0] .CGColor,//浅灰色
(id)[UIColor colorWithWhite:0.90 alpha:1.0] .CGColor]; //浅灰色
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;


I've been trying to figure out a way to draw segments as illustrated in the following image:

I'd like to:

  1. draw the segment
  2. include gradients
  3. include shadows
  4. animate the drawing from 0 to n angle

I've been attempting to do this with CGContextAddArc and similar calls but not getting very far.

Can anyone help ?

解决方案

There are many parts to your question.

Getting the path

Creating the path for such a segment shouldn't be too hard. There are two arcs and two straight lines. I've previously explained how you can break down a path like that so I won't do it here. Instead I'm going to be fancy and create the path by stroking another path. You can of course read the breakdown and construct the path yourself. The arc I'm talking about stroking is the orange arc inside the gray dashed end-result.

To stroke the path we first need it. that is basically as simple as moving to the start point and drawing an arc around the center from the current angle to the angle you want the segment to cover.

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

Then when you have that path (the single arc) you can create the new segment by stroking it with a certain width. The resulting path is going to have the two straight lines and the two arcs. The stroke happens from the center an equal distance inwards and outwards.

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

Drawing

Next up is drawing and there are generally two main choices: Core Graphics in drawRect: or shape layers with Core Animation. Core Graphics is going to give you the more powerful drawing but Core Animation is going to give you the better animation performance. Since paths are involved pure Cora Animation won't work. You will end up with strange artifacts. We can however use a combination of layers and Core Graphics by drawing the the graphics context of the layer.

Filling and stroking the segment

We already have the basic shape but before we add gradients and shadows to it I will do a basic fill and stroke (you have a black stroke in your image).

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

That will put something like this on screen

Adding shadows

I'm going to change the order and do the shadow before the gradient. To draw the shadow we need to configure a shadow for the context and draw fill the shape to draw it with the shadow. Then we need to restore the context (to before the shadow) and stroke the shape again.

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);

At this point the result is something like this

Drawing the gradient

For the gradient we need a gradient layer. I'm doing a very simple two color gradient here but you can customize it all you want. To create the gradient we need to get the colors and the suitable color space. Then we can draw the gradient on top of the fill (but before the stroke). We also need to mask the gradient to the same path as before. To do this we clip the path.

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);

This finishes the drawing as we currently have this result

Animation

When it comes to the animation of the shape it has all been written before: Animating Pie Slices Using a Custom CALayer. If you try doing the drawing by simply animating the path property you are going to see some really funky warping of the path during the animation. The shadow and gradient has been left intact for illustrative purposes in the image below.

I suggest that you take the drawing code that I've posted in this answer and adopt it to the animation code from that article. Then you should end up with the what you are asking for.


For reference: the same drawing using Core Animation

Plain shape

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];

Adding shadows

The layer has some shadow related properties that it's up to you to customize. Howerever you should set the shadowPath property for improved performance.

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

Drawing the gradient

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);

If we drew the gradient now it would be on top of the shape and not inside it. No, we can't have a gradient fill of the shape (I know you were thinking of it). We need to mask the gradient so that it go outside the segment. To do that we create another layer to be the mask of that segment. It has to be another layer, the documentation is clear that the behavior is "undefined" if the mask is part of the layer hierarchy. Since the mask's coordinate system is going to be the same as that of sublayers to the gradient we will have to translate the segment shape before setting it.

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

这篇关于从圆圈或圆环画出片段的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆