首页 > 解决方案 > 如何绘制混合了圆角和尖角的不规则多边形?

问题描述

我希望能够绘制具有锐角和钝角混合的不规则多边形,其中一些角是圆形的,而有些则不是。

假设我有一个这样的多边形:

在此处输入图像描述

我想把它的所有角都画成圆角:

在此处输入图像描述

或每隔一个圆角:

在此处输入图像描述

我怎么做?

David Rönnqvist 写了一篇优秀的、内容丰富的文章,描述了绘制圆角背后的数学原理,但它相当复杂,如果你对三角学和几何学不满意,会导致你的大脑爆炸。

在同一个线程中,Anjali 发布了一个答案CGMutablePath,该答案显示了如何使用该方法比大卫的数学密集型方法更简单地绘制圆角三角形addArc(tangent1End:tangent2End:radius:transform:)

但是,它并没有告诉我如何处理具有可变数量顶点的多边形,或者如何混合圆角和尖角。我怎么做?

标签: ioscore-animationpolygonrounded-corners

解决方案


做到这一点而又不会让自己发疯的关键是方法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。为此,您只需layerClass向自定义UIView子类添加一个类 var:

// This class var causes the view's base layer to be a CAShapeLayer.
class override var layerClass: AnyClass {
    return CAShapeLayer.self
}

为了跟踪点数组,以及哪些点应该是圆的,哪些应该是平滑的,我们将定义一个 struct PolygonPoint

struct PolygonPoint {
    let point: CGPoint
    let isRounded: Bool
}

我们将给我们的类一个PolygonPoints 数组:

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()
    }
}

这是从上面的 s 数组构建多边形路径的代码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 ”。(关联)

它的视图控制器类中也有代码,该代码从顶点数组(CGPoints)开始。它使用顶点数组中每个顶点的开关以及该顶点的标签来填充垂直堆栈视图。然后它构建一个 s 数组PolygonPoint并将其安装到RoundedCornerPolygonView.

如果用户切换任何开关,它会重建PolygonPoints 的数组并将它们传递给 s RoundedCornerPolygonView,后者会重新绘制自身。屏幕如下所示:

在此处输入图像描述


推荐阅读