使用CASpringAnimation为CAShapeLayer路径添加动画效果

4
我有一个自定义的UIView,我将CAShapeLayer作为视图层的直接子层添加进去:

enter image description here

private let arcShapeLayer = CAShapeLayer()

CAShapeLayerawakeFromNib中声明了以下属性(边框是为了更容易地可视化动画):

arcShapeLayer.lineWidth = 2.0
arcShapeLayer.strokeColor = UIColor.black.cgColor
arcShapeLayer.fillColor = UIColor.lightGray.cgColor
arcShapeLayer.masksToBounds = false
layer.masksToBounds = false
layer.addSublayer(arcShapeLayer)

我在layoutSubviews中声明了arcShapeLayer层的框架:
arcShapeLayer.frame = layer.bounds

接下来我有一个函数,我会从ViewController中传入一个百分比,以便在一些拖动后实现弧形效果。简单来说,我会在图层底部添加一个quadCurve

func configureArcPath(curvePercentage: CGFloat) -> CGPath {

    let path = UIBezierPath()
    path.move(to: CGPoint.zero)
    path.addLine(to:
        CGPoint(
            x: arcShapeLayer.bounds.minX,
            y: arcShapeLayer.bounds.maxY
        )
    )

    path.addQuadCurve(
        to:
        CGPoint(
            x: arcShapeLayer.bounds.maxX,
            y: arcShapeLayer.bounds.maxY
        ),
        controlPoint:
        CGPoint(
            x: arcShapeLayer.bounds.midX,
            y: arcShapeLayer.bounds.maxY + arcShapeLayer.bounds.maxY * (curvePercentage * 0.4)
        )
    )

    path.addLine(to:
        CGPoint(
            x: arcShapeLayer.bounds.maxX,
            y: arcShapeLayer.bounds.minY
        )
    )
    path.close()

    return path.cgPath

}

然后我尝试使用弹性效果来为弧线添加动画,代码如下:
func animateArcPath() {

    let springAnimation = CASpringAnimation(keyPath: "path")
    springAnimation.initialVelocity = 10
    springAnimation.mass = 10
    springAnimation.duration = springAnimation.settlingDuration
    springAnimation.fromValue = arcShapeLayer.path
    springAnimation.toValue = configureArcPath(curvePercentage: 0.0)
    springAnimation.fillMode = .both
    arcShapeLayer.add(springAnimation, forKey: nil)

    arcShapeLayer.path = configureArcPath(curvePercentage: 0.0)

}

视频中可以看到的问题是,电弧从未超过其原始位置。虽然它在原始位置和静止位置之间振荡,但弹簧效应从未实现。
我在这里错过了什么?

1
我不知道,但我知道我该怎么做。我会动画化路径绘制视图的控制点,而不是动画化形状层的路径,就像我在这个例子中所做的一样:https://github.com/mattneub/Programming-iOS-Book-Examples/blob/3ecfba2ec68673c125aca6e276a44d43a4cee947/bk2ch04p165customAnimatableProperty3/Triangle/TriangleView.swift - matt
请参阅 https://stackoverflow.com/a/31258793/341994 以查看该项目的视频。在视频中,我没有使用弹簧动画,但那只是一个微不足道的更改。 - matt
是的,听起来就像是我在这个例子中的 case 10:https://github.com/mattneub/Programming-iOS-Book-Examples/blob/3ecfba2ec68673c125aca6e276a44d43a4cee947/bk2ch04p148layerAnimation/ch17p490layerAnimation/ViewController.swift - matt
1
当然,我更想知道为什么你的动画会在零处卡住! - matt
哦,原来这是一个众所周知的限制:https://dev59.com/J5nga4cB1Zd3GeqPTRMQ - matt
显示剩余3条评论
1个回答

4

我试验了一下,你说的完全正确。这与将其钳位于零点无关。可见动画会在两个极端位置都被钳位。

假设您提供了一个大的质量值,使得超调应该远远超过凸形弓的初始位置在返回起点的旅程中。那么我们看到的是动画在原始位置您尝试进行动画处理的最小值处都会被钳制。随着弹簧摆动在它们之间,我们看到动画在一个极端和另一个极端之间发生,但当它达到最大或最小值时,它只是保持在那里,直到它有时间摆动到其全部范围并返回。

我必须得出结论,CAShapeLayer路径不喜欢弹簧动画。这一点也在CAShapeLayer路径弹簧动画未“超调”上提出。

通过链接基本动画,我能够模拟出你可能想要的外观:

输入图片说明

这是我使用的代码(基于您自己的代码):

@IBAction func animateArcPath() {
    self.animate(to:-1, in:0.5, from:arcShapeLayer.path!)
}
func animate(to arc:CGFloat, in time:Double, from current:CGPath) {
    let goal = configureArcPath(curvePercentage: arc)
    let anim = CABasicAnimation(keyPath: "path")
    anim.duration = time
    anim.fromValue = current
    anim.toValue = goal
    anim.delegate = self
    anim.setValue(arc, forKey: "arc")
    anim.setValue(time, forKey: "time")
    anim.setValue(goal, forKey: "pathh")
    arcShapeLayer.add(anim, forKey: nil)
}
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
    if let arc = anim.value(forKey:"arc") as? CGFloat,
        let time = anim.value(forKey:"time") as? Double,
        let p = anim.value(forKey:"pathh") {
        if time < 0.05 {
            return
        }
        self.animate(to:arc*(-0.5), in:time*0.5, from:p as! CGPath)
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接