8

我的设计团队使用摩擦力和张力为我们提供动画参数。例如:

具有弹簧影响(280 张力和 20.5 摩擦)超过 0.3 秒

不幸的是,我总是猜到这些值转换成什么,并注视它,如果它看起来很接近,我将它发送过去并且他们批准它。但是,不断构建具有不同值的项目所花费的时间非常耗时。必须有一个更简单的方法。

我在 Github 上找到了Framer,它让我相信阻尼可以这样计算:

let damping: CGFloat = 20.5 / (2 * (1 * 280)).squareRoot()

但是,我似乎无法弄清楚如何根据摩擦和张力计算速度。有没有更简单的方法可以为这个开发人员节省一些宝贵的时间?

动画示例:

UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: damping,
                       initialSpringVelocity: ???, options: .curveEaseIn, animations: { // Do Stuff })
4

4 回答 4

11

你是对的,你链接的代码可以用来计算阻尼比(我很受宠若惊,因为我是写它的人;)。不过,您的派生 Swift 代码似乎有错误。我认为应该是(注意括号中的差异):

let damping: CGFloat = 20.5 / (2 * (1 * 280).squareRoot())

仅当您想在开始动画时给对象一些初始速度时才需要速度值。这种情况的用例是如果对象在启动动画时已经在移动(例如在拖动交互后启动动画时)。

因此,如果对象从非移动状态开始动画,您可以使用 0 作为初始速度。

我对你的设计团队给你的压力、摩擦持续时间感到困惑。因为弹簧是模拟物理,所以张力和摩擦力将模拟在特定持续时间后停止动画的弹簧。具有张力280和摩擦力的弹簧20.5会导致持续时间接近0.65,而不是0.3。(请参阅computeDurationFramer 中的函数如何根据张力和摩擦计算持续时间)。这是咖啡脚本版本:

# Tries to compute the duration of a spring,
# but can't for certain velocities and if dampingRatio >= 1
# In those cases it will return null
exports.computeDuration = (tension, friction, velocity = 0, mass = 1) ->
    dampingRatio = computeDampingRatio(tension, friction)
    undampedFrequency = Math.sqrt(tension / mass)
    # This is basically duration extracted out of the envelope functions
    if dampingRatio < 1
        a = Math.sqrt(1 - Math.pow(dampingRatio, 2))
        b = velocity / (a * undampedFrequency)
        c = dampingRatio / a
        d = - ((b - c) / epsilon)
        if d <= 0
            return null
        duration = Math.log(d) / (dampingRatio * undampedFrequency)
    else
        return null
    return duration

您可以为 iOS 使用的弹簧指定持续时间的原因是,它根据阻尼比和持续时间计算弹簧的张力和摩擦力。在引擎盖下,它仍将使用张力和摩擦力进行弹簧模拟。要深入了解该代码在 iOS 中的工作原理,请查看computeDerivedCurveOptionsin Framer,它是 iOS 使用的代码的直接端口(通过反汇编和分析 iOS 二进制文件创建)。

于 2018-04-28T21:01:21.107 回答
3

我把它转换成一个方便的 UIView 扩展,所以你可以直接调用 UIView.animate 并带有张力和摩擦力。

extension UIView {
    class func animate(withTension tension: CGFloat, friction: CGFloat, mass: CGFloat = 1.0, delay: TimeInterval = 0, initialSpringVelocity velocity: CGFloat = 0, options: UIView.AnimationOptions = [], animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) {
        let damping = friction / sqrt(2 * (1 * tension))
        let undampedFrequency = sqrt(tension / mass)

        let epsilon: CGFloat = 0.001
        var duration: TimeInterval = 0

        if damping < 1 {
            let a = sqrt(1 - pow(damping, 2))
            let b = velocity / (a * undampedFrequency)
            let c = damping / a
            let d = -((b - c) / epsilon)
            if d > 0 {
                duration = TimeInterval(log(d) / (damping * undampedFrequency))
            }
        }

        UIView.animate(withDuration: duration, delay: delay, usingSpringWithDamping: damping, initialSpringVelocity: velocity, options: options, animations: animations, completion: completion)
    }
}
于 2019-05-03T18:17:43.033 回答
1

我将@Niels 代码翻译成 Swift。

import UIKit

func computeDuration(tension: Double, friction: Double, velocity: Double = 0.0, mass: Double = 1.0) -> Double {
    let dampingRatio = computeDampingRatio(tension: tension, friction: friction)
    let undampedFrequency = sqrt(tension / mass)

    let epsilon = 0.001
    var duration = 0.0

    //This is basically duration extracted out of the envelope functions
    if dampingRatio < 1 {
        let a = sqrt(1 - pow(dampingRatio, 2))
        let b = velocity / (a * undampedFrequency)
        let c = dampingRatio / a
        let d = -((b - c) / epsilon)
        if d <= 0 {
            return duration
        }

        duration = log(d) / (dampingRatio * undampedFrequency)
    }

    return duration
}

func computeDampingRatio(tension: Double, friction: Double) -> Double {
    let damping = friction / sqrt(2 * (1 * tension))
    return damping
}
于 2018-05-14T23:19:46.070 回答
0

一种更简单的方法可以直接使用摩擦和张力来实现这种动画,无需任何计算。

我们UISpringTimingParameters使用 with UIViewPropertyAnimator

  1. 创建 UISpringTimingParameters
let springParameters = UISpringTimingParameters(
  mass: 1.0, 
  stiffness: 260, // tension
  damping: 20,    // friction
  initialVelocity: .init(dx: 0, dy: 1.0)
)

请注意,刚度是张力,阻尼是摩擦。

  1. 创建动画师并添加动画
let animator = UIViewPropertyAnimator(duration: duration, timingParameters: springParameters)
animator.addAnimations {
    animations()
}
animator.startAnimation(afterDelay: delay)

因此,可以进行便利扩展:

extension UIView {
    class func animate(withDuration duration: TimeInterval, tension: CGFloat, friction: CGFloat, mass: CGFloat = 1.0, delay: TimeInterval = 0, initialSpringVelocity velocity:  CGVector = .zero, animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) {
       
        let springParameters = UISpringTimingParameters(
            mass: mass, stiffness: tension,
            damping: friction,
            initialVelocity: .init(dx: 0, dy: 1.0)
        )
        let animator = UIViewPropertyAnimator(duration: duration, timingParameters: springParameters)
        animator.addAnimations {
            animations()
        }
        animator.startAnimation(afterDelay: delay)
    }
}
于 2021-08-23T13:42:13.550 回答