一个带有直观 API 和可读代码的 iOS 动画引擎!(用 Swift 4.0 编写。)
我决定从头开始构建一个动画引擎,理由是:没有其他工具能按我想要的方式完成任务!虽然有些库非常出色,但我的需求非常严格,因为我需要的
pod 'Kinieta', '~> 0.5'
目前,只需复制Kinieta(虚拟)文件夹中的文件即可。
square.move(to: ["x": 374], during: 1.0).easeInOut(.Back).wait(for: 1.0).complete {
square.move(to: ["x": 74])
}
代码中包含的一个扩展在 UIView
上,将提供动画的入口点。接口对象是 Kinieta
,每个视图都有一个。
// This will snap the view to the given coordinates
aView.move(to: ["x": 250, "y": 500])
// This will animate the same view to the coordinates in 0.5 seconds
aView.move(to: ["x": 250, "y": 500], during: 0.5)
// This will delay the start of the animation by 0.5 seconds
aView.move(to: ["x": 250, "y": 500], during: 0.5).delay(for: 0.5)
// And this will ease the whole thing
aView.move(to: ["x": 250, "y": 500], during: 0.5).delay(for: 0.5).easeInOut()
// While this will ease it with a bounce-back
aView.move(to: ["x": 250, "y": 500], during: 0.5).delay(for: 0.5).easeInOut(.Back)
// And call the complete block when the animation is finished
aView.move(to: ["x": 250, "y": 500], during: 0.5).delay(for: 0.5).easeInOut(.Back).complete { print("♥") }
可以动画的UIView属性及其密钥如下所示
密钥 | 值类型 | 计量 | 属性动画 |
---|---|---|---|
"x" | 任意数字 | 屏幕点 | frame.origin.x |
"y" | 任意数字 | 屏幕点 | frame.origin.y |
"w" 或 "width" | 任意数字 | 屏幕点 | frame.size.width |
"h" 或 "height" | 任意数字 | 屏幕点 | frame.size.height |
"a" 或 "alpha" | 任意数字 | 0到1不透明度 | alpha |
"r" 或 "rotation" | 任意数字 | 度 | transform |
"frame" | CGRect | 复合 | frame |
"bg" 或 "background" | UIColor | 颜色 | backgroundColor |
"brc" 或 "borderColor" | UIColor | 颜色 | layer.borderColor |
"brw" 或 "borderWidth" | UIColor | 屏幕点 | layer.borderWidth |
"crd" 或 "cornerRadius" | UIColor | 切边半径 | layer.cornerRadius |
注意:当同一列表中传入两个同义词键(如 "bg" 和 "background")时,较详细的键(即 "background")将获胜,而另一个将被默默忽略。
每个移动都可以通过调用3个缓动函数之一来平滑,并将
// When no curve is passed `.Quad` is used
aView.move(to: ["x": 250, "y": 500], during: 0.5).easeIn()
// Ease at start, end and both ends respectively
aView.move(to: ["x": 250, "y": 500], during: 0.5).easeIn(.Cubic)
aView.move(to: ["x": 250, "y": 500], during: 0.5).easeOut(.Cubic)
aView.move(to: ["x": 250, "y": 500], during: 0.5).easeInOut(.Cubic)
可以传递默认参数以提供要使用的缓动函数,默认的为Quad。所有缓动动画都是基于贝塞尔曲线,大多数已在 Easing.Types
枚举中默认提供。
enum Types {
case Sine
case Quad
case Cubic
case Quart
case Quint
case Expo
case Back
case Custom(Bezier)
}
最后一种类型 .Custom
将捕获自定义贝塞尔曲线并将其用作缓动函数。三次方程(或立方)贝塞尔曲线由4个点组成,称为控制点。前两个和最后两个是约定(0.0, 0.0)和(1.0, 1.0),而另外两个定义曲线的曲率。当然,您不必手动计算这些数字,因为有一些有用的工具可以帮助您完成这项工作,cubic-bezier 就是其中之一。
例如,对于开始非常快然后突然减慢的动画,我使用了从网站上复制并插入Bezier实例的 这个曲线,
let myBezier = Bezier(0.16, 0.73, 0.89, 0.24)
aView.move(to: ["x": 250, "y": 500], during: 1.0).easeInOut(.Custom(myBezier))
传递的所有曲线都已被 预先烘焙到表格中,以提供快速解析!
您可以将几个动画非常容易地组合在一起
let start = ["x": aView.x, "y": aView.y]
aView.move(to: ["x": 250, "y": 500], during: 0.5).easeInOut(.Cubic)
.move(to: ["x": 300, "y": 200], during: 0.5).easeInOut(.Cubic)
.move(to: start, during: 0.5).easeInOut(.Cubic)
可以保存包含动画的字典并将其稍后通过,如上述示例所示。您还可以通过调用具有 wait(for time: TimeInterval)
函数添加动画之间的暂停。
aView.move(to: ["x": 250, "y": 500], during: 0.5).easeInOut(.Cubic)
.wait(for: 0.5)
.move(to: ["x": 300, "y": 200], during: 0.5).easeInOut(.Cubic)
最后,您可以使用 again(times: Int = 1)
函数重复动画序列。
aView.move(to: ["x": 250, "y": 500], during: 0.5).easeInOut(.Cubic)
.move(to: ["x": 300, "y": 200], during: 0.5).easeInOut(.Cubic)
.again()
您可以将各种动画同时运行以实现更复杂的效果。例如,我们可以在移动的末尾添加一个短暂的淡入,并在所有操作完成后调用单个回调
aView.move(to: ["x": 200, "y": 500], during: 1.0).easeInOut(.Cubic)
.move(to: ["a": 0], during: 0.2).delay(for: 0.8).easeOut()
.parallel()
.complete { print("Finished All") }
.parallel()
干的事情是创建一个包含所有 先于调用 的动作的内部组。这可能会在需要顺序运行两个或多个并发组时引起问题。例如
aView.move(to: ["x": 300], during: 1.0).easeInOut() // this needs to run first,
.move(to: ["x": 200], during: 1.0).easeInOut() // then this...
.move(to: ["a": 0], during: 0.2).easeOut() // ...parallel with this!
.parallel()
上面的代码将取 所有三个移动 并并行运行,实际上忽略了第一个。但是,我们想要的却是第一个移动单独运行,接着是其他两个在并行执行。为此,我们以如下方式调用 then
属性
aView.move(to: ["x": 300], during: 1.0).easeInOut()
.then
.move(to: ["x": 200], during: 1.0).easeInOut()
.move(to: ["a": 0], during: 0.2).easeOut()
.parallel()
您可以将多个不同视图的动画组合在一起,并在它们全部完成后获取一个共同的事件处理程序。
Engine.shared.group([
aView.move(to: ["x": 374], during: 1.0).easeInOut(.Cubic)
.move(to: ["a": 0], during: 0.2).delay(for: 0.8).easeOut().parallel(),
otherView.move(to: ["x": 100, "r": 30], during: 1.0).easeInOut(.Cubic)
]) { print("Both Finished") }
请记住,调用Kinieta API的返回值是一个对象,因此也可以这样做
let move1 = aView.move(to: ["x": 374], during: 1.0).easeInOut(.Cubic)
let move2 = otherView.move(to: ["x": 100, "r": 30], during: 1.0).easeInOut(.Cubic)
Engine.shared.group([move1, move2]) { print("Both Finished") }