如何在 CAShapeLayer 上仅填充形状的一部分

Posted

技术标签:

【中文标题】如何在 CAShapeLayer 上仅填充形状的一部分【英文标题】:How to fill only a part of the shape on CAShapeLayer 【发布时间】:2020-04-08 08:38:52 【问题描述】:

我正在使用 UIBezierPath 创建以下形状,然后使用 CAShapeLayer 绘制如下。

然后我可以将 CAShapeLayer fillColor 属性从 shapeLayer.fillColor = UIColor.clear.cgColor 更改为 shapeLayer.fillColor = UIColor.blue.cgColor 并获得以下形状。

代码如下:

import UIKit

class ViewController: UIViewController 

    var line = [CGPoint]()
    let bezierPath: UIBezierPath = UIBezierPath()

    override func viewDidLoad() 
        super.viewDidLoad()
        view.backgroundColor = .white
        let point1 = CGPoint(x: 100, y: 100)
        let point2 = CGPoint(x: 400, y: 100)

        line.append(point1)
        line.append(point2)

        addCircle(toRight: false)
        addLine()
        addCircle(toRight: true)

        let shapeLayer = CAShapeLayer()
        shapeLayer.path = bezierPath.cgPath
        shapeLayer.strokeColor = UIColor.blue.cgColor
        shapeLayer.fillColor = UIColor.clear.cgColor

         self.view.layer.addSublayer(shapeLayer)
    

    func addLine() -> Void 
        bezierPath.move(to: line.first!)
        bezierPath.addLine(to: line.last!)
    

    func addCircle(toRight: Bool) -> Void 
        let angle = CGFloat( Double.pi / 180 )
        let r = CGFloat(20.0)
        let x0 = toRight ? line.first!.x : line.last!.x
        let y0 = toRight ? line.first!.y : line.last!.y
        let x1 =  toRight ? line.last!.x : line.first!.x
        let y1 = toRight ? line.last!.y : line.first!.y
        let x = x1 - x0
        let y = y1 - y0
        let h = (x*x + y*y).squareRoot()
        let x2 = x0 + (x * (h + r) / h)
        let y2 = y0 + (y * (h + r) / h)

        // Add the arc, starting at that same point
        let point2 = CGPoint(x: x2, y: y2)
        let pointZeroDeg = CGPoint(x: x2 + r, y: y2)
        self.bezierPath.move(to: pointZeroDeg)
        self.bezierPath.addArc(withCenter: point2, radius: r,
                          startAngle: 0*angle, endAngle: 360*angle,
                          clockwise: true)
        self.bezierPath.close()
    

但我真正想要的是跟随形状(左侧圆圈已填充,右侧圆圈未填充)。

所以我的问题是,如何在 CAShapeLayer 上只填充部分形状? 是否可以?有什么技巧可以实现吗?

PS:我可以通过创建 3 个不同的 UIBezierPath(用于 leftCircle、line 和 rightCircle)并使用 3 个不同的 CAShapeLayer 绘制它们来实现这一点,如下所示。

// left Circle
shapeLayer1.path = bezierPath1.cgPath
shapeLayer1.fillColor = UIColor.blue.cgColor

// line
shapeLayer2.path = bezierPath2.cgPath

// right Circle
shapeLayer3.path = bezierPath3.cgPath
shapeLayer3.fillColor = UIColor.clear.cgColor

self.view.layer.addSublayer(shapeLayer1)
self.view.layer.addSublayer(shapeLayer2)
self.view.layer.addSublayer(shapeLayer3)

但我更喜欢使用单个 CAShapeLayer 来实现这一点。

【问题讨论】:

【参考方案1】:

我认为更好的方法是使用多种形状。

但是,您可以使用evenOdd 形状填充规则来实现单一形状。

只需在方法addCircle中的右侧圆圈中添加一个额外的圆圈即可:

if toRight 
    self.bezierPath.addArc(withCenter: point2, radius: r,
                      startAngle: 0*angle, endAngle: 2*360*angle,
                      clockwise: true)
 else 
    self.bezierPath.addArc(withCenter: point2, radius: r,
                      startAngle: 0*angle, endAngle: 360*angle,
                      clockwise: true)

并将fillRule 设置为evenOdd

shapeLayer.fillColor = UIColor.blue.cgColor        
shapeLayer.fillRule = .evenOdd

所以你会得到左边的圆圈将有一个绘图圈,并将由evenOdd 规则填充。但是右边的圆圈会有两个绘图圈,并且是空的。

evenOdd 规则 (https://developer.apple.com/documentation/quartzcore/cashapelayerfillrule/1521843-evenodd):

指定奇偶缠绕规则。计算路径总数 过境点。如果交叉的数量是偶数,则该点在外面 路径。如果交叉数是奇数,则该点在 路径和包含它的区域应该被填充。

【讨论】:

以上是关于如何在 CAShapeLayer 上仅填充形状的一部分的主要内容,如果未能解决你的问题,请参考以下文章

在 iOS 上用渐变填充路径

如何通过点击角来改变 CAShapeLayer 中的多边形形状?

如何在iOS上仅在具有清晰颜色的模糊视图底部获得圆角?

如何将 CAGradientLayer 添加到 CAShapeLayer?

CAShapeLayer shadowPath 动画。如何防止动画完成时的阴影填充?

如何在 iOS Swift4 中创建这个圆形?