如何为 UIBezierPath 上的填充颜色变化设置动画?

Posted

技术标签:

【中文标题】如何为 UIBezierPath 上的填充颜色变化设置动画?【英文标题】:How to animate a fill color change on UIBezierPath? 【发布时间】:2016-04-10 20:06:27 【问题描述】:

开始时形状已正确填充,但我不知道如何更改填充颜色,或者更好的是,如何在UIBezierPath 上设置填充颜色更改的动画。就像我正在寻找改变 UIView 的背景颜色一样。

var fillColor = UIColor()

func changeBackgroundColor() 

    let animcolor = CABasicAnimation(keyPath: "fillColor")
    animcolor.fromValue = UIColor.greenColor()
    animcolor.toValue = UIColor.orangeColor()
    animcolor.duration = 1.0;
    animcolor.repeatCount = 0;
    animcolor.autoreverses = true
    shapeLayer.addAnimation(animcolor, forKey: "fillColor")




var fillColor = UIColor()
let clipPath = UIBezierPath()
let shapeLayer = CAShapeLayer()

override func drawRect(rect: CGRect) 

        clipPath.moveToPoint(CGPointMake(self.bounds.minX + 7.65, self.bounds.minY - 0.25))
        clipPath.addCurveToPoint(CGPointMake(self.bounds.minX + 7.65, self.bounds.minY + 36.1), controlPoint1: CGPointMake(self.bounds.minX - 2.38, self.bounds.minY + 9.79), controlPoint2: CGPointMake(self.bounds.minX - 2.38, self.bounds.minY + 26.06))
        clipPath.addCurveToPoint(CGPointMake(self.bounds.minX + 43.99, self.bounds.minY + 36.1), controlPoint1: CGPointMake(self.bounds.minX + 17.69, self.bounds.minY + 46.13), controlPoint2: CGPointMake(self.bounds.minX + 33.96, self.bounds.minY + 46.13))
        clipPath.addLineToPoint(CGPointMake(self.bounds.minX + 43.99, self.bounds.minY + 36.1))
        clipPath.addLineToPoint(CGPointMake(self.bounds.minX + 44.01, self.bounds.minY + 0.19))
        clipPath.addLineToPoint(CGPointMake(self.bounds.minX + 7.58, self.bounds.minY + 0.19))
        clipPath.usesEvenOddFillRule = true
        fillColor = userColor
    

clipPath.addClip()
fillColor.setFill()
clipPath.fill()
shapeLayer.path = clipPath.CGPath
self.layer.mask = shapeLayer

【问题讨论】:

【参考方案1】:

就像我要改变 UIView 的背景颜色一样。

但是为什么你不能完全做到这一点?您已经将视图蒙版到您的路径 - 因此为您的背景颜色设置动画将达到您想要的确切效果。只需摆脱 Core Graphics 路径填充 - 并像 can't use drawRect with an animation of the background 一样删除 drawRect 覆盖。

如果您真的需要使用drawRect 并为颜色设置动画,您可以考虑将UIView 拆分为两个UIViews - 一个用于绘制背景层(您可以对其进行动画处理) ),以及一个绘制自定义绘图。或者,您可以设置CADisplayLink 以使用中间背景颜色重新绘制每一帧的视图——但这在性能方面并不令人惊奇。

正如我在下面所说,您不能直接为UIBezierPath 的填充颜色设置动画。如果您只想使用现有代码更改(而不是动画)填充颜色,那么您只需使用不同的 userColor 调用 setNeedsDisplay

如果您之所以问这个问题是因为您使用两种不同的路径进行遮罩和填充,请参阅我下面的答案(事后看来过于复杂)。


问题是UIBezierPath 的填充颜色不是可动画的。但是CAShapeLayer 上的填充颜色是。

因此,您想使用另一个CAShapeLayer 来代替填充。我建议您创建一个fillLayer 和一个maskLayer 属性,以明确它们的功能。现在因为您使用的是 100% 分层的方法,您可以将代码移出 drawRect,因为您不再进行任何核心图形绘图。

另外,值得注意的是,您需要使用CGColor 来实现值的动画。

这样的事情应该可以解决问题:

var fillColor = UIColor.greenColor()
let maskLayer = CAShapeLayer()
let fillLayer = CAShapeLayer()

func changeBackgroundColor() 

    let animcolor = CABasicAnimation(keyPath: "fillColor")
    animcolor.fromValue = UIColor.greenColor().CGColor
    animcolor.toValue = UIColor.orangeColor().CGColor
    animcolor.duration = 1.0;
    animcolor.repeatCount = 0;
    animcolor.autoreverses = true
    fillLayer.addAnimation(animcolor, forKey: "fillColor")



// re-adjust the clipping path when the view bounds changes
override func layoutSubviews() 

    let clipPath = UIBezierPath()
    clipPath.moveToPoint(CGPointMake(self.bounds.minX + 7.65, self.bounds.minY - 0.25))
    clipPath.addCurveToPoint(CGPointMake(self.bounds.minX + 7.65, self.bounds.minY + 36.1), controlPoint1: CGPointMake(self.bounds.minX - 2.38, self.bounds.minY + 9.79), controlPoint2: CGPointMake(self.bounds.minX - 2.38, self.bounds.minY + 26.06))
    clipPath.addCurveToPoint(CGPointMake(self.bounds.minX + 43.99, self.bounds.minY + 36.1), controlPoint1: CGPointMake(self.bounds.minX + 17.69, self.bounds.minY + 46.13), controlPoint2: CGPointMake(self.bounds.minX + 33.96, self.bounds.minY + 46.13))
    clipPath.addLineToPoint(CGPointMake(self.bounds.minX + 43.99, self.bounds.minY + 36.1))
    clipPath.addLineToPoint(CGPointMake(self.bounds.minX + 44.01, self.bounds.minY + 0.19))
    clipPath.addLineToPoint(CGPointMake(self.bounds.minX + 7.58, self.bounds.minY + 0.19))

    // update layer paths
    fillLayer.path = clipPath.CGPath
    maskLayer.path = clipPath.CGPath



override init(frame: CGRect) 
    super.init(frame: frame)

    // configure filling
    fillLayer.fillColor = fillColor.CGColor
    fillLayer.fillRule = kCAFillRuleEvenOdd

    // add fill path to superlayer
    layer.addSublayer(fillLayer)

    // configure masking layer
    maskLayer.fillRule = kCAFillRuleEvenOdd
    layer.mask = maskLayer

【讨论】:

太棒了。感谢您将其转化为更好的实施。但是我无法将fillLayer 的填充颜色设置为动画,我什至无法更改它。该函数肯定被调用,我正在记录它以确保。这就是我坐下来想我会在 30 到 40 分钟内完成的事情,结果却需要半天时间。 @spacemonkey fillLayer 是否在动画之前显示在屏幕上?确保它的 frame 不为零,并且它已添加到层层次结构中。如果您使用我上面的代码,您需要使用 init(frame:) 初始化程序初始化 UIView(实际上您希望将任何与大小相关的代码移动到更合适的位置,例如 layoutSubviews )。 哦,它就在那里。确切的位置和大小,它应该是。填充颜​​色也设置为应有的颜色,只是不会更改为其他颜色。 嗯……很奇怪。上面的代码在测试项目中对我来说工作正常。您是否确保将 .CGColor 添加到动画的 to 和 from 值? 我知道,这太疯狂了。甚至注释掉了整个班级并逐字输入。【参考方案2】:

Swift 4 版本,你可以在 Playground 中查看代码

var fillColor = UIColor.green
let maskLayer = CAShapeLayer()
let fillLayer = CAShapeLayer()

func changeBackgroundColor() 
    
    let animcolor = CABasicAnimation(keyPath: "fillColor")
    animcolor.fromValue = UIColor.green.cgColor
    animcolor.toValue = UIColor.orange.cgColor
    animcolor.duration = 1.0;
    animcolor.repeatCount = 0;
    animcolor.autoreverses = true
    fillLayer.add(animcolor, forKey: "fillColor")
    


// re-adjust the clipping path when the view bounds changes

let view = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
view.backgroundColor = UIColor.white

func createPaths() 
    let clipPath = UIBezierPath()
    clipPath.move(to: CGPoint(x: view.bounds.minX + 7.65, y: view.bounds.minY - 0.25))
    clipPath.addCurve(to: CGPoint(x: view.bounds.minX + 7.65, y: view.bounds.minY + 36.1),
                      controlPoint1: CGPoint(x: view.bounds.minX - 2.38, y: view.bounds.minY + 9.79),
                      controlPoint2: CGPoint(x: view.bounds.minX - 2.38, y: view.bounds.minY + 26.06))
    clipPath.addCurve(to: CGPoint(x: view.bounds.minX + 43.99, y: view.bounds.minY + 36.1),
                      controlPoint1: CGPoint(x: view.bounds.minX + 17.69, y: view.bounds.minY + 46.13),
                      controlPoint2: CGPoint(x: view.bounds.minX + 33.96, y: view.bounds.minY + 46.13))
    
    clipPath.addLine(to: CGPoint(x: view.bounds.minX + 43.99, y: view.bounds.minY + 36.1))
    clipPath.addLine(to: CGPoint(x: view.bounds.minX + 44.01, y: view.bounds.minY + 0.19))
    clipPath.addLine(to: CGPoint(x: view.bounds.minX + 7.58, y: view.bounds.minY + 0.19))
    
    // update layer paths
    fillLayer.path = clipPath.cgPath
    maskLayer.path = clipPath.cgPath
    
    fillLayer.fillColor = fillColor.cgColor
    fillLayer.fillRule = .evenOdd
    view.layer.addSublayer(fillLayer)
    maskLayer.fillRule = .evenOdd
    view.layer.mask = maskLayer


createPaths()

【讨论】:

以上是关于如何为 UIBezierPath 上的填充颜色变化设置动画?的主要内容,如果未能解决你的问题,请参考以下文章

当鼠标悬停在该特定区域上时,如何为列表中的一个矩形填充不同的颜色?

如何为 CAShapeLayer 路径和填充颜色设置动画

您如何为视图填充的变化设置动画?

如何为svg rect设置填充颜色

如何为图案填充添加背景颜色?

OpenCV:如何为每个像素填充具有每种 RGB 颜色值的 rgb 图像?