AnimationSeries
创建动画链的简单方法。
func drunkAnimation() {
let headbanging = 🧠.rotate(degree: 360, duration: 0.1)
let stumble = (🚶.move(position: CGPoint(x: -10, y: 0), duration: 0.5) + 🚶.move(position: CGPoint(x: 10, y: 0), duration: 0.5)) * 10
let puke = (🤢.sizing(scale: (100, 100), duration: 0.4) + 🤢.sizing(scale: (0.1, 0.1), duration: 0.2)) * 10
let drunk = (headbanging | stumble | puke) * Int.max
drunk.start()
}
需求
iOS 10.0+
安装
CocoaPods
pod 'AnimationSeries'
或者将 AnimationSeries.framework 文件添加到您要使用的项目中。(如果需要请检查“如果需要则复制项目”)
为什么选择 AnimationSeries?
在 iOS 项目中使用循环动画并非易事。
例如,编写一个重复三次闪烁视图的动画代码
private func blinkView3times() {
func appear(_ v: UIView, duration: TimeInterval, completed: ((Bool) -> Void)?) {
UIView.animate(withDuration: duration, animations: {
v.alpha = 1.0
}, completion: completed)
}
func disappear(_ v: UIView, duration: TimeInterval, completed: ((Bool) -> Void)?) {
UIView.animate(withDuration: duration, animations: {
v.alpha = 0.0
}, completion: completed)
}
func blink(_ v: UIView, duration: TimeInterval, completed: ((Bool) -> Void)?) {
disappear(v, duration: 1.0, completed: { _ in
appear(v, duration: 1.0, completed: completed)
})
}
blink(mView, duration: 1.0, completed: { _ in
blink(self.mView, duration: 1.0, completed: { _ in
blink(self.mView, duration: 1.0, completed: nil)
})
})
}
如果需要闪烁视图 100 次,这种方法就不酷了... 或者您可以在循环中给每个动画设置延迟来解决此问题。但是,这种方法计算起来很繁琐。
我们可以使用 AnimationSeries 来直观地解决这个问题。
private func blinkView100times() {
let blink = myView.disappear(duration: 1.0) + myView.appear(duration: 1.0)
let anim = blink * 100
anim.start()
}
它是这样的?这是否看起来更直观和简单?
AnimationSeries 项目是在类似上述困难的情况下制作的。有了这个工具,你可以更容易地制作视图动画。
基础知识
在此项目视图扩展 (.appear, .disappear, .discolor, .move, .rotate, sizing) 中声明了默认动画。
可以使用 + (顺序连接) 和 * (重复) 操作符连接这些动画函数。
单动画
以下任何一个动画都会返回一个 AnimationSeries 实例。调用 start 函数以启动动画。通过设置 complete 回调,你可以获取动画的结束回调。
/// view.alpha -> 1.0 with flat parameters
public func appear(duration: TimeInterval, delay: TimeInterval = 0.0, options: UIView.AnimationOptions = [], _ complete: CompleteCallback? = nil) -> AnimationSeries {
let anim = Appear(self, params: ViewAnimation.Parameter(duration, delay: delay, options: options), complete)
return anim
}
/// view.alpha -> 1.0 with AnimationParameter
public func appear(_ params: ViewAnimation.Parameter, _ complete: CompleteCallback? = nil) -> AnimationSeries {
return self.appear(duration: params.duration, delay: params.delay, options: params.options, complete)
}
(AnimationParameter 是一个包含时间、延迟和选项的结构体。)
组合动画
可以使用加号 (+) 运算符将 AnimationSeries 实例与其他 AnimationSeries 实例组合。
组合实例返回一个新的 AnimationSeries。
调用新实例的 start 方法以启动动画序列。同样,设置新实例的 animationDidFinish 回调可以让你在所有动画完成后获取回调。
(如果你将 CompleteCallback 设置到一个单个的 AnimationSeries,你可以收到一个当它结束时触发的回调。)
private func startInitialAnim() {
let anim = animView.sizing(scale: (40, 40), duration: 0) + animView.sizing(scale: (0.6, 0.6), duration: 1.6, { _ in
print("shrink(single animation) end.")
}) + animView.sizing(scale: (1.0, 1.0), duration: 0.3)
anim.animationDidFinish = { [weak anim] in
print("Intial animation(animation series) end. -> release point")
AnimationPool.shared.release(anim)
}
anim.start()
}
并行动画
如果你想同时启动某些动画,可以使用竖线符号 (|) 连接动画。
let drunk = (headbanging | stumble | puke) * Int.max
循环动画
可以使用乘号 (*) 运算符重复 AnimationSeries 实例。
let series = view.discolor(to: .orange, duration: 1) + view.discolor(to: .yellow, duration: 1) + view.discolor(to: .green, duration: 1) + view.discolor(to: .blue, duration: 1) + view.discolor(to: .purple, duration: 1)
let repeating = series * 10
repeating.start()
清除动画
您可以使用清除函数来停止动画。(需要额外工作才能将视图恢复到原始外观。)
private func clearCurrentAnimation() {
self.currentAnimations.forEach{ $0.clear() }
self.initializeView()
}
private func initializeView() {
animView.transform = CGAffineTransform(rotationAngle: 0)
animView.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
animView.transform = CGAffineTransform(translationX: 0, y: 0)
animView.alpha = 1.0
animView.backgroundColor = .red
}
内存问题
当使用加号或乘号运算符创建 AnimationSeries 实例时,参与系列的 AnimationSeries 实例会保存在静态内存中,以防止它们形成循环引用或在下操作之前从内存中释放。
动画结束后,强烈建议按照以下方式从内存中释放。 (AnimationSeries.clear 函数也会释放内存。)
let anim = self.view.disappear(duration: 1, delay: 0.0, options: .curveLinear, { _ in
// When the animation finishes, the completeCallback will be called.
print("disappear animation end")
})
// single animation flushing
anim.animationDidFinish = { [weak anim] in
// animationDidFinish closure is used to connect with the following animation(recurable) instance.
// If no animation is linked behind, you can flush it from the static memory when this closure is called.
AnimationPool.shared.release(anim)
}
let series = (self.view.disappear(duration: 1) + self.view.appear(duration: 1)) * 10
series.animationDidFinish = { [weak series] in
// animationDidFinish closure of the series is called when the animation ends.
// In this case, release the series from the static memory.
AnimationPool.shared.release(anim)
}
series.start()
注意
调用清除函数后,所有的 AnimationSeries 实例都不能重新启动。
它也是一个引用类型,所以不会被复制。
private func wrongUsage() {
// wrong: blink will not be copied
let blink = animView.disappear(duration: 1) + animView.appear(duration: 1)
let blinks3Times = blink + blink + blink
blinks3Times.start()
}
自定义
您可以创建一个继承自 AnimationSeries 的类来定义您想要的动画。或者将动画添加到视图的扩展中。
import UIKit
import AnimationSeries
extension UIView {
public func move(path: [(CGPoint, ViewAnimation.Parameter)]) -> AnimationSeries? {
guard !path.isEmpty else { return nil }
var sender: AnimationSeries!
path.forEach { tp in
if sender == nil {
sender = self.move(position: tp.0, params: tp.1) + self.move(position: tp.0, params: ViewAnimation.Parameter(0.0))
}else{
sender = sender + self.move(position: tp.0, params: tp.1)
}
}
return sender
}
}
....
private func customMoveAnimation() {
let params = ViewAnimation.Parameter(0.2)
let paths = (0..<10).reduce(into: [(CGPoint, ViewAnimation.Parameter)](), { ary, n in
ary.append((CGPoint(x: ary.count + 10, y: 0), params))
})
let anim = animView.move(path: paths)
anim?.animationDidFinish = { [weak anim] in
print("moving all end..")
AnimationPool.shared.release(anim)
}
anim?.start()
}