前不久看完 Ray 的 《iOS Animations by Tutorials》,加上最近写动画的小心得,总结了几个 Tips。并不包含很难的动画效果,都是一些很基础的,但是有时候又不容易想到的实现方式(或者只是我没想到而已…)。这个 Tips 会持续更新。
因为这里的动画都是我从项目中抽出来的,所以对大家来说,这可能不是最佳实践。具体需求还是要具体分析,这里仅仅提供一种思路。
完整代码下载:
https://github.com/saitjr/STAnimationTips.git
环境信息:
Mac OS X 10.11.3
Xcode 7.2.1
iOS 9.2
一、多个视图的动画有序且无限次执行
动画循环周期如下:
(黑色放大 —— 橘色旋转一周 —— 黑色缩小)
对我的需求来说,最简单的做法是:黑色做动画,橘色等待;橘色动画完成后,黑色动画 reverse。
假设黑色动画时长是 scaleAnimationDuration
,橘色动画的时长是 rotateAnimationDuration
。
- 橘色动画第一次执行,要先等待黑色动画执行完。可以用
beginTime
来达到效果; - 黑色动画先放大,后缩小,可以直接使用
autoreverses = true
; - 我当时遇到的最大的问题,是如何设置等待,因为这个动画中,出现最多的就是等待。
后来发现并不难,等待可以通过在当前的 scaleAnimation
和 rotateAnimation
外面包一个 groupAnimation
来达到效果。
如图(以 scaleAnimation
为例):
如图,在剩下的 groupDuration - scaleDuration
中,将没有动画效果,从而达到等待的目的。接下来便是计算时间。
依然以 scaleAnimation
为例,有几个属性需要注意(rotate 动画同理):
- scale 的
duration
; - scale group 的
duration
; - 谁来设置
autoreverses
; - 谁来设置
repeatCount
;
先说 autoreverses
,如果是 scale 设置,那么执行完放大动画后,并不会等待,而是直接反向,所以,这个属性不能由 scale 设置,而应该由 scale group 来设置。
然后 repeatCount
和 autoreverses
原因大致类似,group 不重复,scale 重复再多也没用。
scale 的 duration
是可以随心所欲的。但是 scale group 的 duration
就需要注意了。
如下图,我大致整理了 scale 和 rotate 之间的关系。
红色为自定义的已知量,黑色为 scale 的相关数据,黄色为 rotate 的相关数据。
来说一下最后 rotate 的时间超出周期范围(虚线框) 的原因: beginTime
只在第一次执行动画的时候有效,所以 rotate 的等待时间应该要把【缩小+放大】的时间让出来。即 scale duration 的两倍。
这一步要仔细思考下,并不难。可以得到以下公式:
let scaleAnimationDuration: NSTimeInterval = 1.0 let rotateAnimationDuration: NSTimeInterval = 3.0 let scaleGroupAnimationDuration = rotateAnimationDuration / 2.0 + scaleAnimationDuration let rotateGroupAnimationDuration = rotateAnimationDuration + scaleAnimationDuration * 2.0
好了,最繁琐的地方已经过了,下面就是写代码了。
// 执行旋转的橘色视图 private func orangeViewAnimation() { let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation") rotateAnimation.fromValue = -M_PI * 2 rotateAnimation.duration = rotateAnimationDuration let groupAnimation = CAAnimationGroup() groupAnimation.beginTime = CACurrentMediaTime() + scaleAnimationDuration groupAnimation.duration = rotateAnimationDuration + scaleAnimationDuration * 2 groupAnimation.repeatCount = Float.infinity groupAnimation.animations = [rotateAnimation] orangeView.layer.addAnimation(groupAnimation, forKey: "rotateAnimation") } // 执行缩放的黑色视图 private func blackViewAnimation() { let scaleAnimation = CABasicAnimation(keyPath: "transform.scale") scaleAnimation.fromValue = 1 scaleAnimation.toValue = 0.5 // 因为在放大以后,要停留在放大的状态,等待橘色视图旋转,所以使用 fillMode 和 removedOnCompletion 两个属性来将动画停留在最终的状态 scaleAnimation.fillMode = kCAFillModeForwards scaleAnimation.removedOnCompletion = false scaleAnimation.duration = scaleAnimationDuration let groupAnimation = CAAnimationGroup() groupAnimation.duration = scaleAnimationDuration + rotateAnimationDuration / 2.0 groupAnimation.repeatCount = Float.infinity groupAnimation.autoreverses = true groupAnimation.animations = [scaleAnimation] blackView.layer.addAnimation(groupAnimation, forKey: "scaleAnimation") }
第一个 Tips 就到这里。完整代码可以查看:
https://github.com/saitjr/STAnimationTips/blob/master/STAnimationTips/ViewControllers/GroupAnimationVC.swift