这段时间在看 ray 的 《iOS Animation by Tutorials》 这本书,把以前完全不熟悉的动画学习了下。然后动手尝试了一些简单的加载动画。本文总结一基础动画的分析过程和实现。
Github :
https://github.com/saitjr/STLoadingGroup
环境信息:
Mac OS X 10.11.2
Xcode 7.2
iOS 9.2
Swift 2
正文:
一、慢动作效果
二、拆分与组合
动画一共可以拆分成三个部分:
- 白线开始绘制,逐渐形成圆( aniamtion-1 )
- 白线的起始点变化,追上之前绘制的线,圆环逐渐消失( animation-2 )
- 每次出现的起点顺时针旋转了 1/4 个圆( animation-3 )
这三个动画的共同点:
-
animation-1和 animation-2 的
duration
相同(如果一定要纠结,其实看到的效果 animation-2 应该会比 animation-1 后执行,因为 animation-2 是快速追上 animation-1 的,但这可以通过设置CABasicAnimation
的fromValue
来处理成同时执行,详见 《iOS Animation by Tutorials》的 Chapter 15 Stoke and Path Animations )。 - 三个动画
repeatCount
相同。都是无限循环,可以将repeatCount
设置为Float.infinity
来达到效果。 - 执行动画的对象相同,均作用在同一个 layer。
三、选择
1.选择作用对象
关于选用 UIView
, CALayer
还是 CAShapeLayer
,首先需要清楚他们的特点和当前需求的符合度。
需求:根据上面的分析,我们需要对象有以下特点:
- 圆环起点和终点的控制,需要用到
stokeEnd
和stokeStart
属性。 -
不需要响应用户交互事件。
- 需要设置线宽、背景色、线头尾的圆角样式。
虽然使用 UIView
的 darwRect
方法也能达到效果。但是 UIView
为 layer 的管理者,并且可以捕捉事件(当然这只是其中一个特点)。这一特点我们明显不需要。所以,选择 CAShapeLayer
来实现。
2.选择动画
既然选择了 layer,那么动画肯定选择 CA
开头的类。其中 CAAnimation
、 CABasicAnimation
、 CAKeyframeAnimation
等动画都有自己的特点。 因为只需要简单的控制 stokeEnd
与 stokeStart
,所以 CABasicAnimation
已经能满足要求。
四、实现
1.绘制圆环路径
cycleLayer.lineCap = kCALineCapRound // 设置圆角 cycleLayer.lineJoin = kCALineJoinRound // 设置圆角 cycleLayer.lineWidth = STConfiguration.LineWidth // LineWidth 是提前定义的线宽常量 cycleLayer.fillColor = UIColor.clearColor().CGColor cycleLayer.strokeColor = STConfiguration.MainColor.CGColor // MainColor 是提前定义的主题色常量 cycleLayer.strokeEnd = 0 layer.addSublayer(cycleLayer)
以上代码分别设置了圆环 layer 的圆角、线宽、填充色(透明)、边框色( MainColor
)、初始化终点位置。
对于一条线的绘制,需要的就是起点和终点。iOS 中,控制这两个点分别是 stokeEnd
和 stokeStart
属性。所以最初终点 stokeEnd = 0
。
2.动画
先来看看更改起点和终点动画的慢动作效果,然后再根据之前的分析,做动画。
其中,控制两个点的 stokeEnd
和 stokeStart
分别位于:
所以,先对 stokeEnd
进行动画,随后 stokeStart
追上它。这个“随后”怎么处理呢?最直观的就是延迟,设置动画的 beginTime
,但是这里没必要。 stokeStart
和 stokeEnd
的有效值均为 0 ~ 1, 所以,想要有延迟的效果,将动画的 fromValue
设置为负值开始即可。具体负多少,就要看 stokeStart
什么时候追上 stokeEnd
了。
let strokeStartAnimation = CABasicAnimation(keyPath: "strokeStart") strokeStartAnimation.fromValue = -1 strokeStartAnimation.toValue = 1.0 let strokeEndAnimation = CABasicAnimation(keyPath: "strokeEnd") strokeEndAnimation.fromValue = 0 strokeEndAnimation.toValue = 1.0 let animationGroup = CAAnimationGroup() animationGroup.duration = STConfiguration.AnimationDuration // 动画时长是提前定义的常量 animationGroup.repeatCount = Float.infinity animationGroup.animations = [strokeStartAnimation, strokeEndAnimation] cycleLayer.addAnimation(animationGroup, forKey: "animationGroup")
最后,是旋转动画。在画线的过程中,同时也在做旋转,所以每次 stokeStart
开始的地方才改变了。再来看一下效果:
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation") rotateAnimation.fromValue = 0 rotateAnimation.toValue = M_PI * 2 rotateAnimation.repeatCount = Float.infinity rotateAnimation.duration = STConfiguration.AnimationDuration * 4 cycleLayer.addAnimation(rotateAnimation, forKey: "rotateAnimation")
圆环绘制一圈,要变 1/4 个角度,所以, 旋转一圈需要 4 * 绘制一圈的时长。
到此,这个动画最关键的部分就完成了。其他逻辑下载源码进行查看 :
https://github.com/saitjr/STLoadingGroup