刚看到这个动画的时候,脑海里出现了两个方案,一种是通过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来实现线条长短的动画,下面是效果图和代码:
/**
线的第一步动画,线长从长变短
*/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属性,来实现向中间聚拢的效果,下面是效果图和代码:
/**
线的第二步动画,线向中间平移
*/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来实现线条长短的动画,下面是效果图和代码:
/**
线的第三步动画,线由短变长
*/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属性来控制动画的执行顺序,还加了动画暂停和继续的功能,效果和代码见下图:
//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地址