如何避免旋转动画在 2π 边界处跳跃?

Posted

技术标签:

【中文标题】如何避免旋转动画在 2π 边界处跳跃?【英文标题】:How to avoid rotation animation jumping at 2π boundary? 【发布时间】:2016-10-18 12:09:10 【问题描述】:

我正在使用 CAKeyframeAnimation 沿 CGPath 为箭头设置动画。箭头跟随路径,atan2 'flip' 从 0 到 2π 的输出除外,反之亦然。在这种情况下,箭头会在继续之前完成完整的 360° 旋转。

有什么方法可以计算我的 CAKeyframeAnimation 的值,以便箭头始终旋转最小的量以到达它要去的地方?

        let turnAnimation = CAKeyframeAnimation(keyPath: "transform.rotation")
        turnAnimation.duration = 5

        var values = [CGFloat]()
        for i in 0..<points.count - 1 

            let point1 = points[i]
            let point2 = points[i + 1]
            let delta = point2 - point1
            let value = atan2(delta.y, delta.x)

            values.append(value)
        

        turnAnimation.values = values
        turnAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
        turnAnimation.repeatCount = Float.greatestFiniteMagnitude
        arrowPointLayer.transform = CATransform3DMakeRotation(values.first!, 0, 0, 1)
        arrowPointLayer.add(turnAnimation, forKey: "rotateArrow")

编辑:根据答案更新,但仍不能正常工作:

let turnAnimation = CAKeyframeAnimation(keyPath: "transform.rotation")
        turnAnimation.duration = 5

        var rawValues = [CGFloat]()
        var values = [CGFloat]()
        for i in 0..<points.count - 1 

            let point1 = points[i]
            let point2 = points[i + 1]
            let delta = point2 - point1
            let rawValue = atan2(delta.y, delta.x)

            var value: CGFloat

            if i == 0 
                value = 0 // we will apply this value from rawValues
             else 
                let lastValue = rawValues.last!
                value = rawValue - lastValue
            

            if value < 0 
                value = .pi * 2 + value
            

            rawValues.append(rawValue)

            values.append(value)
        

        for (i, p) in values.enumerated() 
            print("\(rawValues[i]): \(p)")
        

        turnAnimation.values = values
        turnAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
        turnAnimation.repeatCount = Float.greatestFiniteMagnitude
        turnAnimation.isAdditive = true

        arrowPointLayer.transform = CATransform3DMakeRotation(rawValues.first!, 0, 0, 1)
        arrowPointLayer.add(turnAnimation, forKey: "rotateArrow")

【问题讨论】:

您好,我突然想到问题可能是使用"transform.rotation" 作为关键路径。 Apple 建议不要这样做。 真的吗?相反,只是rotation 只是变换。您可以使用值函数来指定 z 旋转。 很高兴知道,谢谢!你有“苹果建议”部分的链接吗?我想阅读他们关于这一切的信息以供将来参考。 这是多年前的 WWDC 视频。他说使用“transform.rotation.z”键的连续旋转可能会导致数学问题。这就是为什么我在书中的这个例子中使用价值函数:apeth.com/iosBook/ch17.html#_keyframe_animation 【参考方案1】:

不要将values 设为绝对旋转值列表,而是将其设为旋转差异 列表并将动画的isAdditive 设置为true。这样一来,就在告诉箭头转向哪个方向以及转向多少。

我是这样测试的(在我的测试中,v 只是我界面中的一些视图):

let turnAnimation = CAKeyframeAnimation(keyPath: "transform.rotation")
turnAnimation.duration = 5
let values : [CGFloat] = [0,2,-1,2,-1,3,-3]
turnAnimation.values = values
turnAnimation.timingFunction = 
    CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
turnAnimation.isAdditive = true
self.v.layer.add(turnAnimation, forKey: "rotateArrow")

视图的行为完全符合我的预期。

【讨论】:

从概念上讲这很棒!不幸的是,当我实现它时,箭头完全停止旋转 ::scratches head:: 我不知道你的代码现在是什么样子的。我只知道它最初的样子。我向你保证它确实有效。我将添加一个简单的示例作为测试。 很酷,谢谢,但我也很想知道出了什么问题。 :) 您可能会通过直接设置transform 属性来杀死动画。你会注意到我没有在我的代码中这样做。作为一个实验,你可以剪掉那条线,看看它是否有帮助。 让我们continue this discussion in chat。

以上是关于如何避免旋转动画在 2π 边界处跳跃?的主要内容,如果未能解决你的问题,请参考以下文章

平滑的 CSS 过渡动画旋转

动画文本跟随路径[关闭]

Android ScrollView 在旋转动画上丢失剪辑

Webkit CSS 动画跳跃问题

应该如何在动画中旋转UIView呢?

顺序 UIView 转换