RxSwift中UIView与动画的结合

试想一个实际场景,先将视图旋转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)

虽然整体的代码量比之前的实现方式还多,但是动画实现部分看起来很舒服,而且使用什么动画以及使用多少个动画都可以由自己来决定,代码的可扩展和重用性都强。而且函数封装的动画越多,越容易组合动画效果实现复杂的动画。

2018-12-12 16-01-46.2018-12-12 16_02_05.gif

另外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)
2018-12-12 16-00-16.2018-12-12 16_00_29.gif

如果我们需要在移动之前先旋转几次,这也是很容易实现的,先实现无限旋转,然后只取前面需要的次数即可

 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)
2018-12-12 15-59-13.2018-12-12 15_59_29.gif

为了让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)

最后可以根据需求实现自己需要的动画函数,添加到扩展中,方便代码功能的复用

参考

RxSwift and Animations in iOS

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明AI阅读 16,054评论 3 119
  • 年复一年,终于把自己蹉跎成奔三微胖界的一员。并没有说胖的惨不忍睹,直到有一天…… 某个阳光灿烂的日子,一行人打算出...
    唐来来lailai阅读 1,342评论 0 0
  • 花如雪,尽无言,为君一笑叹悲欢 樱花落,红尘看,我再把君叹 箫声散,听弦断,再断一曲听悲欢 潇雨寒,笙歌散,寂寞已...
    听闻千风阅读 2,593评论 0 2
  • 七绝·缘来(柿)你 秋随雁去迹无踪, 万里晴空柿子红。 落尽繁华情意现, 陪君一世笑寒风。
    酉时七若阅读 1,873评论 2 10
  • 在极限编程中有一个很有意思的实践,就是隐喻。他是指在系统架构、设计和开发过程中将一些重要、关键、复杂的概念,用隐喻...
    7in10阅读 7,011评论 1 51

友情链接更多精彩内容