使用自定义 UIBezierPath 动画视图边框

Posted

技术标签:

【中文标题】使用自定义 UIBezierPath 动画视图边框【英文标题】:Animate view borders with custom UIBezierPath 【发布时间】:2018-08-20 11:58:32 【问题描述】:

我正在尝试制作一个带有聊天“气泡”的应用,我需要为每个气泡状态之间的过渡设置动画。

extension UIView 
    func makeRoundedCorners(topLeftRad: Double, topRightRad: Double, bottomRightRad: Double, bottomLeftRad: Double) 
        let path = UIBezierPath(roundedRect: self.bounds, topLeftRadius: CGFloat(topLeftRad), topRightRadius: CGFloat(topRightRad), bottomRightRadius: CGFloat(bottomRightRad), bottomLeftRadius: CGFloat(bottomLeftRad))
        let maskLayer = CAShapeLayer()
        maskLayer.path = path.cgPath

        let animation = CABasicAnimation(keyPath: "path")
        animation.toValue = path.cgPath
        animation.duration = 1

        maskLayer.add(animation, forKey: "makeRoundedCorners")
        self.layer.mask = maskLayer
    


extension UIBezierPath 
    convenience init(roundedRect rect: CGRect, topLeftRadius r1: CGFloat, topRightRadius r2: CGFloat, bottomRightRadius r3: CGFloat, bottomLeftRadius r4: CGFloat) 
        let left  = CGFloat(Double.pi)
        let up    = CGFloat(1.5*Double.pi)
        let down  = CGFloat(Double.pi / 2)
        let right = CGFloat(0.0)
        self.init()

        addArc(withCenter: CGPoint(x: rect.minX + r1, y: rect.minY + r1), radius: r1, startAngle: left,  endAngle: up,    clockwise: true)
        addArc(withCenter: CGPoint(x: rect.maxX - r2, y: rect.minY + r2), radius: r2, startAngle: up,    endAngle: right, clockwise: true)
        addArc(withCenter: CGPoint(x: rect.maxX - r3, y: rect.maxY - r3), radius: r3, startAngle: right, endAngle: down,  clockwise: true)
        addArc(withCenter: CGPoint(x: rect.minX + r4, y: rect.maxY - r4), radius: r4, startAngle: down,  endAngle: left,  clockwise: true)
        close()
    

所以我编写了一个自定义 UIBeziePath Initializer,然后按照描述为 UIView 添加了一个扩展。

但是当我尝试更新单元格的状态时,什么都没有发生,它只是立即绘制路径。我该怎么办?

我附上了一些图片来说明发生了什么

我弄错了,将初始路径替换为maskLayer.path = UIBezierPath(roundedRect: self.bounds, cornerRadius: 5).cgPath

但现在这件事正在发生

【问题讨论】:

【参考方案1】:

动画立即发生的原因是同一路径分配给maskLayer,并用作动画的toValue

let maskLayer = CAShapeLayer()
maskLayer.path = path.cgPath // same path used here...

let animation = CABasicAnimation(keyPath: "path")
animation.toValue = path.cgPath // ... as here
animation.duration = 1

maskLayer.add(animation, forKey: "makeRoundedCorners")
self.layer.mask = maskLayer

由于该路径是动画的预期最终值,因此您需要提供一个值来制作动画。

注意:路径动画是“未定义的” 如果两条路径具有不同数量的控制点或线段。

【讨论】:

其实如果不这样做的话,在动画结束的时候,maskLayer的路径会和动画之前一样。 正确。当动画完成并被移除时,图层将再次呈现/显示其模型值(在这种情况下:路径属性的值)。我并不是建议 OP 不应该将路径分配给图层。我只是说隐式的 from value 和显式的 to value 都是一样的,这使得动画看起来是即时的。 谢谢!我弄错了,但是当我添加不同的路径时,会发生奇怪的事情。我会在一分钟内更新我的问题 @DavidRönnqvist 如果您有空,请查看更新后的问题 @MaxKraev 是的,正如我在回答末尾提到的那样:“请注意,如果两条路径具有不同数量的控制点或线段,则路径动画是“未定义的”。”【参考方案2】:

您需要指定动画对象的fromValue 属性。 这样,Core Animation 就知道如何在 fromto 值之间进行插值。

let animation = CABasicAnimation(keyPath: #keyPath(CAShapeLayer.path))
animation.fromValue = UIBezierPath(…).path
animation.toValue = path.cgPath
animation.duration = 1

顺便说一句,如果您想使用UIBezierPath 生成带有漂亮圆角的矩形(又名松鼠),您可能需要查看我的extension of UIBezierPath。

【讨论】:

谢谢!我肯定会考虑这种可能性)实际上现在发生了奇怪的事情。我已经更新了问题 幸运的是,我非常了解这个问题!这是因为点的顺序或/和 CGPath 的点数。你应该试试我的扩展程序,我解决了这个特殊问题。

以上是关于使用自定义 UIBezierPath 动画视图边框的主要内容,如果未能解决你的问题,请参考以下文章

在自定义绘制的视图中动画大小变化的问题

如何在布局转换期间为集合视图单元格的边框宽度/颜色设置动画?

带有部分边框的 UIView 边框

如何在 Swift 中未知宽度和高度的视图中居中 UIBezierPath 椭圆?

使用 CGAffineTransformScale 自定义 UIView 缩放

访问在 UIView 上绘制的 UIBezierPath