问题描述
我希望能够绘制混合锐角和钝角的不规则多边形,其中一些角是圆的,有些则不是。
我想把它画成圆角:
或者每隔一个圆角:
我该怎么做?
David Rönnqvist 写了一篇出色的 very informative article,描述了绘制圆角背后的数学原理,但它非常复杂,如果您对三角学和几何学不熟悉,它会导致您的大脑爆炸。
在同一个线程 Anjali posted an answer 中,它展示了如何使用 CGMutablePath
方法 addArc(tangent1End:tangent2End:radius:transform:)
然而,它没有告诉我如何处理顶点数量可变的多边形,或者如何混合圆角和尖角。我该怎么做?
解决方法
在不让自己发疯的情况下做到这一点的关键是方法addArc(tangent1End:tangent2End:radius:transform:)
。这将向现有的 CGMutablePath
添加弧。该方法从路径的当前点开始。您指定一个点 tangent1End
,它是您要为其绘制圆角的顶点,以及另一个点 tangent2End
,它是您的 pologon 中的下一个顶点。
要绘制具有可变顶点数的多边形,我们使用点数组。
为了使所有的角变圆,您必须将路径的起点设置为多边形直线段之一上的某个点,然后在终点返回该点。计算2点的中点很容易:
let midpoint = CGPoint(x: (point1.x + point2.x)/2,y: (point1.y + point2.y)/2 )
所以我们将路径的当前点移动到顶点数组中第一个和最后一个点之间的中点作为起点:
let midpoint = CGPoint(x: (first.point.x + last.point.x) / 2,y: (first.point.y + last.point.y) / 2 )
path.move(to: midpoint)
然后,对于多边形中的每个顶点,我们要么只画一条线到那个点(对于尖角),要么使用美妙的、易于使用的addArc(tangent1End:tangent2End:radius:transform:)
画一条线段,结束有一个圆弧,围绕该顶点。
我将创建 UIView 的子类 RoundedCornerPolygonView。
class RoundedCornerPolygonView: UIView {
}
它将自己设置为使其内容层成为 CAShapeLayer。为此,您只需将类 var layerClass
添加到您的自定义 UIView
子类:
// This class var causes the view's base layer to be a CAShapeLayer.
class override var layerClass: AnyClass {
return CAShapeLayer.self
}
为了跟踪点数组,哪些点应该四舍五入,哪些应该平滑,我们将定义一个结构PolygonPoint
:
struct PolygonPoint {
let point: CGPoint
let isRounded: Bool
}
我们将为我们的类提供一个 PolygonPoint
数组:
public var points = [PolygonPoint]()
并添加一个 didSet 方法来更新我们的形状图层的路径,如果它发生变化:
public var points = [PolygonPoint]() {
didSet {
guard points.count >= 3 else {
print("Polygons must have at least 3 sides.")
return
}
buildPolygon()
}
}
这是从上面的 PolygonPoint
数组构建多边形路径的代码:
/// Rebuild our polygon's path and install it into our shape layer.
private func buildPolygon() {
guard points.count >= 3 else { return }
drawPoints() // Draw each vertex into another layer if requested.
let first = points.first!
let last = points.last!
let path = CGMutablePath()
// Start at the midpoint between the first and last vertex in our polygon
// (Since that will always be in the middle of a straight line segment.)
let midpoint = CGPoint(x: (first.point.x + last.point.x) / 2,y: (first.point.y + last.point.y) / 2 )
path.move(to: midpoint)
//Loop through the points in our polygon.
for (index,point) in points.enumerated() {
// If this vertex is not rounded,just draw a line to it.
if !point.isRounded {
path.addLine(to: point.point)
} else {
//Draw an arc from the previous vertex (the current point),around this vertex,and pointing to the next vertex.
let nextIndex = (index+1) % points.count
let nextPoint = points[nextIndex]
path.addArc(tangent1End: point.point,tangent2End: nextPoint.point,radius: cornerRadius)
}
}
// Close the path by drawing a line from the last vertex/corner to the midpoint between the last and first point
path.addLine(to: midpoint)
// install the path into our (shape) layer
let layer = self.layer as! CAShapeLayer
layer.path = path
}
我在 Github 上创建了一个示例项目,该项目实现了上面定义的 RoundedCornerPolygonView
类,并允许您选择在运行时应该圆角还是平滑的角。
该项目名为“RoundedCornerPolygon”。 (链接)
它的视图控制器类中还有代码,从顶点数组 (CGPoint
s) 开始。它使用顶点数组中每个顶点的开关填充垂直堆栈视图,以及该顶点的标签。然后构建一个 PolygonPoint
数组并将其安装到 RoundedCornerPolygonView
中。
如果用户切换任何开关,它会重建 PolygonPoint
数组并将它们传递给 RoundedCornerPolygonView
,后者重新绘制自身。
画面如下: