试想一个实际场景,先将视图旋转180度,然后视图暗淡消失,一般情况下我们可能会选择在动画完成的闭包中再添加暗淡动画,如下:
UIView.animate(withDuration: 0.5, animations: {
self.animatableView.transform = CGAffineTransform(rotationAngle: .pi / 2)
}, completion: { _ in
UIView.animate(withDuration: 0.5, animations: {
self.animatableView.alpha = 0
})
})
目前代码看起来还OK,实现也很简单,并不是很复杂,但是如果需要再插入一个动画呢,那么有可能变成这样
UIView.animate(withDuration: 0.5, animations: {
self.animatableView.transform = CGAffineTransform(rotationAngle: .pi / 2)
}, completion: { _ in
UIView.animate(withDuration: 0.5, animations: {
self.animatableView.frame = self.animatableView.frame.offsetBy(dx: 50, dy: 0)
}, completion: { _ in
UIView.animate(withDuration: 0.5, animations: {
self.animatableView.alpha = 0
})
})
})
这样看起来很不舒服,有些笨重。如果还需要更多的动画效果,那么可读性就相对差了。当然你可能会想到对于上面的代码,我们其实可以使用关键字动画来处理
UIView.animateKeyframes(withDuration: 1.5, delay: 0, options: [], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.33, animations: {
self.animatableView.transform = CGAffineTransform(rotationAngle: .pi / 2)
})
UIView.addKeyframe(withRelativeStartTime: 0.33, relativeDuration: 0.33, animations: {
self.animatableView.frame = self.animatableView.frame.offsetBy(dx: 50, dy: 0)
})
UIView.addKeyframe(withRelativeStartTime: 0.66, relativeDuration: 0.34, animations: {
self.animatableView.alpha = 0
})
})
虽然看起来直接、清晰一些,但是扩展性不够。接下来看看RxSwift的链式动画实现
封装每一个动画成为一个函数,并返回一个Observable<Void>,一旦动画执行完成被观察者将发送元素告知,这样很容易就实现动画链式效果
旋转函数
func rotate(_ view: UIView, duration: TimeInterval) -> Observable<Void> {
return Observable.create { (observer) -> Disposable in
UIView.animate(withDuration: duration, animations: {
view.transform = CGAffineTransform(rotationAngle: .pi/2)
}, completion: { _ in
observer.onNext(())
observer.onCompleted()
})
return Disposables.create()
}
}
移动函数
func shift(_ view: UIView, duration: TimeInterval) -> Observable<Void> {
return Observable.create { (observer) -> Disposable in
UIView.animate(withDuration: duration, animations: {
view.frame = view.frame.offsetBy(dx: 50, dy: 0)
}, completion: { _ in
observer.onNext(())
observer.onCompleted()
})
return Disposables.create()
}
}
暗淡函数
func fade(_ view: UIView, duration: TimeInterval) -> Observable<Void> {
return Observable.create { (observer) -> Disposable in
UIView.animate(withDuration: duration, animations: {
view.alpha = 0
}, completion: { _ in
observer.onNext(())
observer.onCompleted()
})
return Disposables.create()
}
}
实现了上面的函数,现在就可以组合起来使用了
rotate(animatableView, duration: 0.5)
.flatMap { [unowned self] in
self.shift(self.animatableView, duration: 0.5)
}
.flatMap { [unowned self] in
self.fade(self.animatableView, duration: 0.5)
}
.subscribe()
.disposed(by: disposeBag)
虽然整体的代码量比之前的实现方式还多,但是动画实现部分看起来很舒服,而且使用什么动画以及使用多少个动画都可以由自己来决定,代码的可扩展和重用性都强。而且函数封装的动画越多,越容易组合动画效果实现复杂的动画。
另外RxSwift提供了很多操作符号,非常方便进行各种功能操作,比如:concat
Observable.concat([
rotate(animatableView, duration: 0.5),
shift(animatableView, duration: 0.5),
fade(animatableView, duration: 0.5)
])
.subscribe()
.disposed(by: disposeBag)
使用该操作符能够实现相同的效果,如果我们需要加入适当的延时操作,也是非常方便的
// 封装延时函数
func delay(_ duration: TimeInterval) -> Observable<Void> {
return Observable.of(()).delay(duration, scheduler: MainScheduler.instance)
}
// 添加延时
Observable.concat([
rotate(animatableView, duration: 0.5),
delay(0.5),
shift(animatableView, duration: 0.5),
delay(1.0),
fade(animatableView, duration: 0.5)
])
.subscribe()
.disposed(by: disposeBag)
如果我们需要在移动之前先旋转几次,这也是很容易实现的,先实现无限旋转,然后只取前面需要的次数即可
func rotateEndlessly(_ view: UIView, duration: TimeInterval) -> Observable<Void> {
var disposed = false
return Observable.create { (observer) -> Disposable in
func animate() {
UIView.animate(withDuration: duration, animations: {
view.transform = view.transform.rotated(by: .pi/2)
}, completion: { (_) in
observer.onNext(())
if !disposed {
animate()
}
})
}
animate()
return Disposables.create {
disposed = true
}
}
}
// 实现旋转5次
Observable.concat([
rotateEndlessly(animatableView, duration: 0.5).take(5),
shift(animatableView, duration: 0.5),
fade(animatableView, duration: 0.5)
])
.subscribe()
.disposed(by: disposeBag)
为了让UIView类使用我们自己封装的动画效果,我们可以为Reactive添加扩展
extension Reactive where Base == UIView {
func rotate(duration: TimeInterval) -> Observable<Void> {
return Observable.create { (observer) -> Disposable in
UIView.animate(withDuration: duration, animations: {
self.base.transform = CGAffineTransform(rotationAngle: .pi/2)
}, completion: { _ in
observer.onNext(())
observer.onCompleted()
})
return Disposables.create()
}
}
func shift(duration: TimeInterval) -> Observable<Void> {
return Observable.create { (observer) -> Disposable in
UIView.animate(withDuration: duration, animations: {
self.base.frame = self.base.frame.offsetBy(dx: 50, dy: 0)
}, completion: { (_) in
observer.onNext(())
observer.onCompleted()
})
return Disposables.create()
}
}
func fade(duration: TimeInterval) -> Observable<Void> {
return Observable.create { (observer) -> Disposable in
UIView.animate(withDuration: duration, animations: {
self.base.alpha = 0
}, completion: { (_) in
observer.onNext(())
observer.onCompleted()
})
return Disposables.create()
}
}
func rotateEndlessly(duration: TimeInterval) -> Observable<Void> {
var disposed = false
return Observable.create { (observer) -> Disposable in
func animate() {
UIView.animate(withDuration: duration, animations: {
self.base.transform = self.base.transform.rotated(by: .pi/2)
}, completion: { (_) in
observer.onNext(())
if !disposed {
animate()
}
})
}
animate()
return Disposables.create {
disposed = true
}
}
}
}
直接使用即可
Observable.concat([
animatableView.rx.rotateEndlessly(duration: 0.5).take(5),
animatableView.rx.shift(duration: 0.5),
animatableView.rx.fade(duration: 0.5)
])
.subscribe()
.disposed(by: disposeBag)
最后可以根据需求实现自己需要的动画函数,添加到扩展中,方便代码功能的复用