在iOS中做动画,还比较简单。因为苹果已经提供好了api,设置相应的参数就好。如下,一句简单的代码,就可做frame的动画。
UIView.animate(withDuration: 0.3) {
button.frame.size = CGSize(width: 200, height: 200)
}
但是,如果我们想要在动画结束之后,再做其他的动画,就需要在completion block中嵌套。写法上不太直观,也不利于阅读。当这种需求越多,block的嵌套也就越深。代码会看的很费劲。
UIView.animate(withDuration: 0.3, animations: {
button.alpha = 1
}, completion: { _ in
UIView.animate(withDuration: 0.3) {
button.frame.size = CGSize(width: 200, height: 200)
}
})
于是,就出现了声明式的api。
声明式:简单,易读,便于理解。把我们要做的事情写好,一目了然。
声明式动画
下面的代码,看起来就清晰多了。
button.animate([
.fadeIn(duration: 0.3),
.resize(to: CGSize(width: 200, height: 200), duration: 0.3)
])
fadeIn:做alpha的动画。
resize:做缩放的动画。
实现
准备
新建playground。因为我们要看到实时的view,所以需要import PlaygroundSupport。新建view后,再添加一句。打开Assistant Editor(带2个圆圈的按钮),在右边就可以看得到view。
PlaygroundPage.current.liveView = view
数据结构
既然要做声明式的api,那就需要将UIView.animate这个方法封装起来,在内部调用。所以需要一个结构来存储动画时间,如何做动画(block)。
public struct Animation {
public let duration: TimeInterval
public let closure: (UIView) -> Void
}
fadeIn,resize动画,只是封装一层而已。
public extension Animation {
static func fadeIn(duration: TimeInterval = 0.3) -> Animation {
return Animation(duration: duration, closure: { $0.alpha = 1 })
}
static func resize(to size: CGSize, duration: TimeInterval = 0.3) -> Animation {
return Animation(duration: duration, closure: { $0.bounds.size = size })
}
}
动画
扩展UIView,添加animate方法,这样每个view就可以直接调用。参数是Animation的数组,目的是为了可以执行一系列的动画。在UIView.animate的completion block中递归调用animate,形成one by one的动画。
public extension UIView {
func animate(_ animations: [Animation]) {
// Exit condition: once all animations have been performed, we can return
guard !animations.isEmpty else {
return
}
// Remove the first animation from the queue
var animations = animations
let animation = animations.removeFirst()
// Perform the animation by calling its closure
UIView.animate(withDuration: animation.duration, animations: {
animation.closure(self)
}, completion: { _ in
// Recursively call the method, to perform each animation in sequence
self.animate(animations)
})
}
}
Demo
fadeIn先执行完,resize再执行。
let animationView = UIView(frame: CGRect(
x: 0, y: 0,
width: 50, height: 50
))
animationView.backgroundColor = .red
animationView.alpha = 0
view.addSubview(animationView)
animationView.animate([
.fadeIn(duration: 3),
.resize(to: CGSize(width: 200, height: 200), duration: 3)
])
并行动画
上面的实现是队列式的动画,一个动画执行完之后,才会执行另外一个。若要实现并行的动画呢?也简单,就不用等到动画结束后才去执行下一个,直接便利执行即可。
public extension UIView {
func animate(inParallel animations: [Animation]) {
for animation in animations {
UIView.animate(withDuration: animation.duration) {
animation.closure(self)
}
}
}
}
调用:
animationView.animate(inParallel: [
.fadeIn(duration: 3),
.resize(to: CGSize(width: 200, height: 200), duration: 3)
])
参考:
Building a declarative animation framework in Swift - Part 1