ios 动画

刚看到这个动画的时候,脑海里出现了两个方案,一种是通过drawRect画出来,然后配合CADisplayLink不停的绘制线的样式;第二种是通过CAShapeLayer配合CAAnimation来实现动画效果。再三考虑觉得使用后者,因为前者需要计算很多,比较复杂,而且经过测试前者相比于后者消耗更多的CPU,下面将我的思路写下来:

相关配置和初始化方法

在写这个动画之前,我们把先需要的属性写好,比如线条的粗细,动画的时间等等,下面是相关的配置和初识化方法:

//线的宽度varlineWidth:CGFloat =0//线的长度varlineLength:CGFloat =0//边距varmargin:CGFloat =0//动画时间varduration:Double =2//动画的间隔时间varinterval:Double =1//四条线的颜色varcolors:[UIColor] = [UIColor.init(rgba:"#9DD4E9") , UIColor.init(rgba:"#F5BD58"),  UIColor.init(rgba:"#FF317E") , UIColor.init(rgba:"#6FC9B5")]//动画的状态private(set)varstatus:AnimationStatus = .Normal//四条线privatevarlines:[CAShapeLayer] = []enumAnimationStatus {//普通状态caseNormal//动画中caseAnimating//暂停casepause    }//MARK: Initial Methodsconvenience init(fram: CGRect , colors: [UIColor]) {        self.init()        self.frame = frame        self.colors = colors        config()    }overrideinit(frame: CGRect) {        super.init(frame: frame)        config()    }    required init?(coder aDecoder: NSCoder) {        super.init(coder: aDecoder)        config()    }privatefuncconfig() {        lineLength = max(frame.width, frame.height)        lineWidth  = lineLength/6.0margin    = lineLength/4.5+ lineWidth/2drawLineShapeLayer()        transform = CGAffineTransformRotate(CGAffineTransformIdentity, angle(-30))    }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

通过CAShapeLayer绘制线条

看到这个线条我就想到了用CAShapeLayer来处理,因为CAShapeLayer完全可以实现这种效果,而且它的strokeEnd的属性可以用来实现线条的长度变化的动画,下面上绘制四根线条的代码:

//MARK: 绘制线/**

绘制四条线

*/privatefuncdrawLineShapeLayer() {//开始点let startPoint = [point(lineWidth/2, y: margin),                          point(lineLength - margin, y: lineWidth/2),                          point(lineLength - lineWidth/2, y: lineLength - margin),                          point(margin, y: lineLength - lineWidth/2)]//结束点let endPoint  = [point(lineLength - lineWidth/2, y: margin) ,                        point(lineLength - margin, y: lineLength - lineWidth/2) ,                        point(lineWidth/2, y: lineLength - margin) ,                        point(margin, y: lineWidth/2)]fori in0...3{            let line:CAShapeLayer = CAShapeLayer()            line.lineWidth  = lineWidth            line.lineCap    = kCALineCapRound            line.opacity    =0.8line.strokeColor = colors[i].CGColor            line.path        = getLinePath(startPoint[i], endPoint: endPoint[i]).CGPath            layer.addSublayer(line)            lines.append(line)        }    }/**

获取线的路径

- parameter startPoint: 开始点

- parameter endPoint:  结束点

- returns: 线的路径

*/privatefuncgetLinePath(startPoint: CGPoint, endPoint: CGPoint) -> UIBezierPath {        let path = UIBezierPath()        path.moveToPoint(startPoint)        path.addLineToPoint(endPoint)returnpath    }privatefuncpoint(x:CGFloat , y:CGFloat) -> CGPoint {returnCGPointMake(x, y)    }privatefuncangle(angle: Double) -> CGFloat {returnCGFloat(angle *  (M_PI/180))    }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

执行完后就跟上图一样的效果了~~~

动画分解

经过分析,可以将动画分为四个步骤:

画布的旋转动画,旋转两圈

线条由长变短的动画,更画布选择的动画一起执行,旋转一圈的时候结束

线条的位移动画,线条逐渐向中间靠拢,再画笔旋转完一圈的时候执行,两圈的时候结束

线条由短变长的动画,画布旋转完两圈的时候执行

第一步画布旋转动画

这里我们使用CABasicAnimation基础动画,keyPath作用于画布的transform.rotation.z,以z轴为目标进行旋转,下面是效果图和代码:

//MARK: 动画步骤/**

旋转的动画,旋转两圈

*/privatefuncangleAnimation() {        let angleAnimation                = CABasicAnimation.init(keyPath:"transform.rotation.z")        angleAnimation.fromValue          = angle(-30)        angleAnimation.toValue            = angle(690)        angleAnimation.fillMode            = kCAFillModeForwards        angleAnimation.removedOnCompletion =falseangleAnimation.duration            = duration        angleAnimation.delegate            = self        layer.addAnimation(angleAnimation, forKey:"angleAnimation")    }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

第二步线条由长变短的动画

这里我们还是使用CABasicAnimation基础动画,keyPath作用于线条的strokeEnd属性,让strokeEnd从1到0来实现线条长短的动画,下面是效果图和代码:

ios 动画_第1张图片

/**

线的第一步动画,线长从长变短

*/privatefunclineAnimationOne() {        let lineAnimationOne                = CABasicAnimation.init(keyPath:"strokeEnd")        lineAnimationOne.duration            = duration/2lineAnimationOne.fillMode            = kCAFillModeForwards        lineAnimationOne.removedOnCompletion =falselineAnimationOne.fromValue          =1lineAnimationOne.toValue            =0fori in0...3{            let lineLayer = lines[i]            lineLayer.addAnimation(lineAnimationOne, forKey:"lineAnimationOne")        }    }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

第三步线条的位移动画

这里我们也是使用CABasicAnimation基础动画,keyPath作用于线条的transform.translation.x和transform.translation.y属性,来实现向中间聚拢的效果,下面是效果图和代码:

ios 动画_第2张图片

/**

线的第二步动画,线向中间平移

*/privatefunclineAnimationTwo() {foriin0...3{varkeypath ="transform.translation.x"ifi%2==1{                keypath ="transform.translation.y"}letlineAnimationTwo = CABasicAnimation.init(keyPath: keypath)            lineAnimationTwo.beginTime = CACurrentMediaTime() + duration/2lineAnimationTwo.duration = duration/4lineAnimationTwo.fillMode = kCAFillModeForwards            lineAnimationTwo.removedOnCompletion =falselineAnimationTwo.autoreverses =truelineAnimationTwo.fromValue =0ifi <2{                lineAnimationTwo.toValue = lineLength/4}else{                lineAnimationTwo.toValue = -lineLength/4}letlineLayer = lines[i]            lineLayer.addAnimation(lineAnimationTwo, forKey:"lineAnimationTwo")        }//三角形两边的比例letscale = (lineLength -2*margin)/(lineLength - lineWidth)foriin0...3{varkeypath ="transform.translation.y"ifi%2==1{                keypath ="transform.translation.x"}letlineAnimationTwo = CABasicAnimation.init(keyPath: keypath)            lineAnimationTwo.beginTime = CACurrentMediaTime() + duration/2lineAnimationTwo.duration = duration/4lineAnimationTwo.fillMode = kCAFillModeForwards            lineAnimationTwo.removedOnCompletion =falselineAnimationTwo.autoreverses =truelineAnimationTwo.fromValue =0ifi ==0|| i ==3{                lineAnimationTwo.toValue = lineLength/4* scale            }else{                lineAnimationTwo.toValue = -lineLength/4* scale            }letlineLayer = lines[i]            lineLayer.addAnimation(lineAnimationTwo, forKey:"lineAnimationThree")        }    }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

第四步线条恢复的原来长度的动画

这里我们还是使用CABasicAnimation基础动画,keyPath作用于线条的strokeEnd属性,让strokeEnd从0到1来实现线条长短的动画,下面是效果图和代码:

ios 动画_第3张图片

/**

线的第三步动画,线由短变长

*/privatefunclineAnimationThree() {//线移动的动画let lineAnimationFour                = CABasicAnimation.init(keyPath:"strokeEnd")        lineAnimationFour.beginTime            = CACurrentMediaTime() + duration        lineAnimationFour.duration            = duration/4lineAnimationFour.fillMode            = kCAFillModeForwards        lineAnimationFour.removedOnCompletion =falselineAnimationFour.fromValue          =0lineAnimationFour.toValue            =1fori in0...3{ifi ==3{                lineAnimationFour.delegate = self            }            let lineLayer = lines[i]            lineLayer.addAnimation(lineAnimationFour, forKey:"lineAnimationFour")        }    }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

最后一步需要将动画组合起来

关于动画组合我没用到CAAnimationGroup,因为这些动画并不是加到同一个layer上,再加上动画类型有点多加起来也比较麻烦,我就通过动画的beginTime属性来控制动画的执行顺序,还加了动画暂停和继续的功能,效果和代码见下图:

ios 动画_第4张图片

//MARK: Public Methods/**

开始动画

*/func startAnimation() {        angleAnimation()        lineAnimationOne()        lineAnimationTwo()        lineAnimationThree()    }/**

暂停动画

*/func pauseAnimation() {        layer.pauseAnimation()forlineLayer in lines {            lineLayer.pauseAnimation()        }        status = .pause    }/**

继续动画

*/func resumeAnimation() {        layer.resumeAnimation()forlineLayer in lines {            lineLayer.resumeAnimation()        }        status = .Animating    }    extension CALayer {//暂停动画func pauseAnimation() {// 将当前时间CACurrentMediaTime转换为layer上的时间, 即将parent time转换为localtimelet pauseTime = convertTime(CACurrentMediaTime(), fromLayer: nil)// 设置layer的timeOffset, 在继续操作也会使用到timeOffset    = pauseTime// localtime与parenttime的比例为0, 意味着localtime暂停了speed        =0;    }//继续动画func resumeAnimation() {        let pausedTime = timeOffset        speed          =1timeOffset    =0;        beginTime      =0// 计算暂停时间let sincePause = convertTime(CACurrentMediaTime(), fromLayer: nil) - pausedTime// local time相对于parent time时间的beginTimebeginTime      = sincePause    }}//MARK: Animation Delegateoverridefunc animationDidStart(anim: CAAnimation) {iflet animation = anim as? CABasicAnimation {ifanimation.keyPath =="transform.rotation.z"{                status = .Animating            }        }    }overridefunc animationDidStop(anim: CAAnimation, finished flag: Bool) {iflet animation = anim as? CABasicAnimation {ifanimation.keyPath =="strokeEnd"{ifflag {                    status = .Normal                    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(interval) * Int64(NSEC_PER_SEC)), dispatch_get_main_queue(), {ifself.status != .Animating {                            self.startAnimation()                        }                    })                }            }        }    }//MARK: Overrideoverridefunc touchesEnded(touches: Set, withEvent event: UIEvent?) {        switch status {case.Animating:            pauseAnimation()case.pause:            resumeAnimation()case.Normal:            startAnimation()        }    }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

总结

动画看起来挺复杂,但是细细划分出来也就那么回事,在写动画之前要先想好动画的步骤,这个很关键,希望大家通过这篇博客可以学到东西,有什么好的建议可以随时提出来,谢谢大家阅读~~demo地址

你可能感兴趣的:(ios 动画)