我正在为我的动画使用下面的代码:
let animation = CABasicAnimation()
animation.keyPath = "position.x"
animation.fromValue = 0
animation.toValue = 300
animation.timingFunction = .init(name: .easeInEaseOut)
animation.duration = 2
nsView.layer?.add(animation, forKey: "basic")
如果您查看在 2 秒内看到的代码,我们正在从 0 变为 300,我想访问介于两者之间的计算值,例如:
0.0, 0.2, 0.3, ..., 300.0
和 CABasicAnimation 那么我该怎么做呢?
CAMediaTimingFunction
s 只是三次贝塞尔曲线。正如您从维基百科页面中看到的那样,曲线由四个控制点参数化定义,我们可以使用 getControlPoint
.
之后,我们可以根据参数t得到曲线y坐标的方程。但现在不是时候。相反,曲线的 x 坐标是动画的时间。所以我们需要用 x 坐标来表示 t。我们已经可以使用控制点(参见维基百科上的方程)用三次方程用 t 表示 x,所以我们只需要求解方程。 这告诉你从哪里开始。
最后,我们只需要代入x,也就是时间,得到参数t,然后代入y坐标的方程。
综上所述,我们可以编写这样的函数:
func getCallableFunction(fromTimingFunction function: CAMediaTimingFunction) -> (Float) -> Float {
// Each *pair* of elements in cps store a control point.
// the elements at even indices store the x coordinates
// the elements at odd indices store the y coordinates
var cps = Array(repeating: Float(0), count: 8)
cps.withUnsafeMutableBufferPointer { pointer in
for i in 0..<4 {
function.getControlPoint(at: i, values: &pointer[i * 2])
}
}
return { x in
// assuming the curve doesn't do weird things like loop around on itself, this only has one real root
// coefficients got from https://pomax.github.io/bezierinfo/#yforx
// implementation of cubicSolve from https://gist.github.com/kieranb662/b85aada0b1c03a06ad2f83a0291d7243
let t = Float(cubicSolve(
a: Double(-cps[0] + cps[2] * 3 - cps[4] * 3 + cps[6]),
b: Double(cps[0] * 3 - cps[2] * 6 + cps[4] * 3),
c: Double(-cps[0] * 3 + cps[2] * 3),
d: Double(cps[0] - x)
).first(where: \.isReal)!.real)
// getting y from t, see equation on Wikipedia
return powf(1 - t, 3) * cps[1] +
powf(1 - t, 2) * t * cps[3] * 3 +
(1 - t) * t * t * cps[5] * 3 +
t * t * t * cps[7]
}
}
请注意,我使用了 this gist 中的立方求解器。随意使用另一个更好的算法。
default
计时功能的用法示例:
let function = getCallableFunction(fromTimingFunction: .init(name: .default))
for i in stride(from: Float(0), through: 1, by: 0.05) {
let result = function(i)
print(result) // this prints a result between 0 and 1
}
在图表上绘制结果如下所示:
default
的文档中显示的图形非常相似。
要将其应用于特定动画,只需线性缩放函数的时间和输出。例如,对于持续时间为 2 且动画在 0 到 300 之间的动画:
let result = function(time / 2) * 300