如何绘制混合有圆角和尖角的不规则多边形?

问题描述

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

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

enter image description here

我想把它画成圆角:

enter image description here

或者每隔一个圆角:

enter image description here

我该怎么做?

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”。 (链接)

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

如果用户切换任何开关,它会重建 PolygonPoint 数组并将它们传递给 RoundedCornerPolygonView,后者重新绘制自身。 画面如下:

enter image description here

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...