我的设计团队使用摩擦和张力为我们提供动画参数。例如:
有弹簧影响(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 })
答案 0 :(得分:7)
你是对的,你链接的代码可以用来计算dampRatio(我很受宠若惊,因为我是写它的人;)。但是,您似乎在派生的Swift代码中有错误。我认为应该是(注意括号中的差异):
let damping: CGFloat = 20.5 / (2 * (1 * 280).squareRoot())
只有在开始动画时想要给对象一些初始速度时才需要速度值。用例是在启动动画时对象是否已经移动(例如在拖动交互后启动动画时)。
因此,如果对象从非移动状态开始动画,则可以使用0作为初始速度。
我很困惑你的设计团队给你一个紧张,摩擦 和 的持续时间。由于弹簧是模拟物理学,因此张力和摩擦力将模拟弹簧,该弹簧将在特定持续时间后停止动画。张力为280
且摩擦力为20.5
的弹簧会导致持续时间接近0.65
,而非0.3
。 (参见Framer中的computeDuration
函数如何计算张力和摩擦的持续时间)。这是coffeescript版本:
# 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使用的弹簧指定持续时间的原因是它根据dampRatio和持续时间计算弹簧的张力。在引擎盖下,它仍将使用张力和摩擦力进行弹簧模拟。要了解该代码在iOS中的工作原理,请查看Framer中的computeDerivedCurveOptions
,它是iOS使用的代码的直接端口(通过反汇编和分析iOS二进制文件创建)。
答案 1 :(得分: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
}
答案 2 :(得分:1)
我将其转换为方便的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)
}
}