6

这是针对 iPad 应用程序的,但它本质上是一道数学题。

我需要绘制一个不同(单调增加)线宽的圆弧。在曲线的开始,它会有一个起始厚度(比如说 2pts),然后厚度会平滑地增加,直到弧线的末端达到最大厚度(比如说 12pts)。

我认为最好的方法是创建一个 UIBezierPath 并填充形状。我的第一次尝试是使用两个圆弧(具有偏移中心),并且在 90° 以内都可以正常工作,但圆弧通常在 90° 到 180° 之间,因此这种方法不会削减它。

厚度增加的 90 度圆弧示例

我目前的方法是使用贝塞尔四边形或三次曲线制作一个轻微的螺旋形(一个从圆弧略微增长,一个略微缩小)。问题是我将控制点放在哪里,以便圆弧的偏差(也就是形状“厚度”)是我想要的值。

约束:

  • 形状必须能够以任意角度开始和结束(彼此在 180° 以内)
  • 形状的“厚度”(与圆的偏差)必须以给定值开始和结束
  • “厚度”必须单调增加(不能变大再变小)
  • 它必须看起来光滑,不能有任何急弯

我也对其他解决方案持开放态度。

4

2 回答 2

5

我的方法只是构建 2 个圆弧并填充其间的区域。棘手的一点是找出这些弧的中心和半径。如果厚度不太大,看起来还不错。(剪切和粘贴,并自行决定是否满足您的需求。)可以通过使用剪切路径来改进。

- (void)drawRect:(CGRect)rect
{
  CGContextRef context = UIGraphicsGetCurrentContext();

  CGMutablePathRef path = CGPathCreateMutable();

  // As appropriate for iOS, the code below assumes a coordinate system with
  // the x-axis pointing to the right and the y-axis pointing down (flipped from the standard Cartesian convention).
  // Therefore, 0 degrees = East, 90 degrees = South, 180 degrees = West,
  // -90 degrees = 270 degrees = North (once again, flipped from the standard Cartesian convention).
  CGFloat startingAngle = 90.0;  // South
  CGFloat endingAngle = -45.0;   // North-East
  BOOL weGoFromTheStartingAngleToTheEndingAngleInACounterClockwiseDirection = YES;  // change this to NO if necessary

  CGFloat startingThickness = 2.0;
  CGFloat endingThickness = 12.0;

  CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
  CGFloat meanRadius = 0.9 * fminf(self.bounds.size.width / 2.0, self.bounds.size.height / 2.0);

  // the parameters above should be supplied by the user
  // the parameters below are derived from the parameters supplied above

  CGFloat deltaAngle = fabsf(endingAngle - startingAngle);

  // projectedEndingThickness is the ending thickness we would have if the two arcs
  // subtended an angle of 180 degrees at their respective centers instead of deltaAngle
  CGFloat projectedEndingThickness = startingThickness + (endingThickness - startingThickness) * (180.0 / deltaAngle);

  CGFloat centerOffset = (projectedEndingThickness - startingThickness) / 4.0;
  CGPoint centerForInnerArc = CGPointMake(center.x + centerOffset * cos(startingAngle * M_PI / 180.0),
                                          center.y + centerOffset * sin(startingAngle * M_PI / 180.0));
  CGPoint centerForOuterArc = CGPointMake(center.x - centerOffset * cos(startingAngle * M_PI / 180.0),
                                          center.y - centerOffset * sin(startingAngle * M_PI / 180.0));

  CGFloat radiusForInnerArc = meanRadius - (startingThickness + projectedEndingThickness) / 4.0;
  CGFloat radiusForOuterArc = meanRadius + (startingThickness + projectedEndingThickness) / 4.0;

  CGPathAddArc(path,
               NULL,
               centerForInnerArc.x,
               centerForInnerArc.y,
               radiusForInnerArc,
               endingAngle * (M_PI / 180.0),
               startingAngle * (M_PI / 180.0),
               !weGoFromTheStartingAngleToTheEndingAngleInACounterClockwiseDirection
               );

  CGPathAddArc(path,
               NULL,
               centerForOuterArc.x,
               centerForOuterArc.y,
               radiusForOuterArc,
               startingAngle * (M_PI / 180.0),
               endingAngle * (M_PI / 180.0),
               weGoFromTheStartingAngleToTheEndingAngleInACounterClockwiseDirection
               );

  CGContextAddPath(context, path);

  CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
  CGContextFillPath(context);

  CGPathRelease(path);  
}
于 2012-07-08T04:24:09.710 回答
1

一种解决方案是手动生成折线。这很简单,但它的缺点是如果控件以高分辨率显示,则必须按比例增加生成的点数。我对 iOS 的了解不够,无法为您提供 iOS/ObjC 示例代码,但这里有一些 python 式的伪代码:

# lower: the starting angle
# upper: the ending angle
# radius: the radius of the circle

# we'll fill these with polar coordinates and transform later
innerSidePoints = []
outerSidePoints = []

widthStep = maxWidth / (upper - lower)
width = 0

# could use a finer step if needed
for angle in range(lower, upper):
    innerSidePoints.append(angle, radius - (width / 2))
    outerSidePoints.append(angle, radius + (width / 2))
    width += widthStep

# now we have to flip one of the arrays and join them to make
# a continuous path.  We could have built one of the arrays backwards
# from the beginning to avoid this.

outerSidePoints.reverse()
allPoints = innerSidePoints + outerSidePoints # array concatenation

xyPoints = polarToRectangular(allPoints) # if needed
于 2012-07-07T20:20:32.283 回答