【问题标题】:Determine springWithDamping and initialSpringVelocity based off from friction and tension根据摩擦和张力确定 springWithDamping 和 initialSpringVelocity
【发布时间】:2018-10-06 08:48:24
【问题描述】:

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

具有弹簧影响(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 })

【问题讨论】:

    标签: swift xcode animation uiviewanimation framerjs


    【解决方案1】:

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

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

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

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

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

    # 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 中的工作原理,请查看 Framer 中的 computeDerivedCurveOptions,它是 iOS 使用的代码的直接端口(通过反汇编和分析 iOS 二进制文件创建)。

    【讨论】:

    • 非常感谢!您能否在答案中提供 computeDuration 函数,我会将其标记为已回答。 :)
    【解决方案2】:

    我将@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
    }
    

    【讨论】:

      【解决方案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)
          }
      }
      

      【讨论】:

        【解决方案4】:

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

        UISpringTimingParametersUIViewPropertyAnimator 一起使用。

        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)
            }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-01-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-07-21
          • 2015-06-13
          相关资源
          最近更新 更多