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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351

推荐阅读更多精彩内容

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