iOS动画教你编写Slack的Loading动画进阶篇

2025-05-29 0 32

前几天看了一篇关于动画的博客叫手摸手教你写 slack 的 loading 动画,看着挺炫,但是是安卓版的,寻思的着仿造着写一篇ios版的,下面是我写这个动画的分解~

老规矩先上图和demo地址:

iOS动画教你编写Slack的Loading动画进阶篇

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

相关配置和初始化方法

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

?

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
//线的宽度

var linewidth:cgfloat = 0

//线的长度

var linelength:cgfloat = 0

//边距

var margin:cgfloat = 0

//动画时间

var duration:double = 2

//动画的间隔时间

var interval:double = 1

//四条线的颜色

var colors:[uicolor] = [uicolor.init(rgba: "#9dd4e9") , uicolor.init(rgba: "#f5bd58"), uicolor.init(rgba: "#ff317e") , uicolor.init(rgba: "#6fc9b5")]

//动画的状态

private(set) var status:animationstatus = .normal

//四条线

private var lines:[cashapelayer] = []

enum animationstatus {

//普通状态

case normal

//动画中

case animating

//暂停

case pause

}

//mark: initial methods

convenience init(fram: cgrect , colors: [uicolor]) {

self.init()

self.frame = frame

self.colors = colors

config()

}

override init(frame: cgrect) {

super.init(frame: frame)

config()

}

required init?(coder adecoder: nscoder) {

super.init(coder: adecoder)

config()

}

private func config() {

linelength = max(frame.width, frame.height)

linewidth = linelength/6.0

margin = linelength/4.5 + linewidth/2

drawlineshapelayer()

transform = cgaffinetransformrotate(cgaffinetransformidentity, angle(-30))

}

通过cashapelayer绘制线条

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

iOS动画教你编写Slack的Loading动画进阶篇

?

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
//mark: 绘制线

/**

绘制四条线

*/

private func drawlineshapelayer() {

//开始点

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)]

for i in 0...3 {

let line:cashapelayer = cashapelayer()

line.linewidth = linewidth

line.linecap = kcalinecapround

line.opacity = 0.8

line.strokecolor = colors[i].cgcolor

line.path = getlinepath(startpoint[i], endpoint: endpoint[i]).cgpath

layer.addsublayer(line)

lines.append(line)

}

}

/**

获取线的路径

- parameter startpoint: 开始点

- parameter endpoint: 结束点

- returns: 线的路径

*/

private func getlinepath(startpoint: cgpoint, endpoint: cgpoint) -> uibezierpath {

let path = uibezierpath()

path.movetopoint(startpoint)

path.addlinetopoint(endpoint)

return path

}

private func point(x:cgfloat , y:cgfloat) -> cgpoint {

return cgpointmake(x, y)

}

private func angle(angle: double) -> cgfloat {

return cgfloat(angle * (m_pi/180))

}

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

动画分解

经过分析,可以将动画分为四个步骤:
•画布的旋转动画,旋转两圈
•线条由长变短的动画,更画布选择的动画一起执行,旋转一圈的时候结束
•线条的位移动画,线条逐渐向中间靠拢,再画笔旋转完一圈的时候执行,两圈的时候结束
•线条由短变长的动画,画布旋转完两圈的时候执行

第一步画布旋转动画

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

iOS动画教你编写Slack的Loading动画进阶篇

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14
//mark: 动画步骤

/**

旋转的动画,旋转两圈

*/

private func angleanimation() {

let angleanimation = cabasicanimation.init(keypath: "transform.rotation.z")

angleanimation.fromvalue = angle(-30)

angleanimation.tovalue = angle(690)

angleanimation.fillmode = kcafillmodeforwards

angleanimation.removedoncompletion = false

angleanimation.duration = duration

angleanimation.delegate = self

layer.addanimation(angleanimation, forkey: "angleanimation")

}

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

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

iOS动画教你编写Slack的Loading动画进阶篇

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15
/**

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

*/

private func lineanimationone() {

let lineanimationone = cabasicanimation.init(keypath: "strokeend")

lineanimationone.duration = duration/2

lineanimationone.fillmode = kcafillmodeforwards

lineanimationone.removedoncompletion = false

lineanimationone.fromvalue = 1

lineanimationone.tovalue = 0

for i in 0...3 {

let linelayer = lines[i]

linelayer.addanimation(lineanimationone, forkey: "lineanimationone")

}

}

第三步线条的位移动画

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

iOS动画教你编写Slack的Loading动画进阶篇

?

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
/**

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

*/

private func lineanimationtwo() {

for i in 0...3 {

var keypath = "transform.translation.x"

if i%2 == 1 {

keypath = "transform.translation.y"

}

let lineanimationtwo = cabasicanimation.init(keypath: keypath)

lineanimationtwo.begintime = cacurrentmediatime() + duration/2

lineanimationtwo.duration = duration/4

lineanimationtwo.fillmode = kcafillmodeforwards

lineanimationtwo.removedoncompletion = false

lineanimationtwo.autoreverses = true

lineanimationtwo.fromvalue = 0

if i < 2 {

lineanimationtwo.tovalue = linelength/4

}else {

lineanimationtwo.tovalue = -linelength/4

}

let linelayer = lines[i]

linelayer.addanimation(lineanimationtwo, forkey: "lineanimationtwo")

}

//三角形两边的比例

let scale = (linelength - 2*margin)/(linelength - linewidth)

for i in 0...3 {

var keypath = "transform.translation.y"

if i%2 == 1 {

keypath = "transform.translation.x"

}

let lineanimationtwo = cabasicanimation.init(keypath: keypath)

lineanimationtwo.begintime = cacurrentmediatime() + duration/2

lineanimationtwo.duration = duration/4

lineanimationtwo.fillmode = kcafillmodeforwards

lineanimationtwo.removedoncompletion = false

lineanimationtwo.autoreverses = true

lineanimationtwo.fromvalue = 0

if i == 0 || i == 3 {

lineanimationtwo.tovalue = linelength/4 * scale

}else {

lineanimationtwo.tovalue = -linelength/4 * scale

}

let linelayer = lines[i]

linelayer.addanimation(lineanimationtwo, forkey: "lineanimationthree")

}

}

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

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

iOS动画教你编写Slack的Loading动画进阶篇

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20
/**

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

*/

private func lineanimationthree() {

//线移动的动画

let lineanimationfour = cabasicanimation.init(keypath: "strokeend")

lineanimationfour.begintime = cacurrentmediatime() + duration

lineanimationfour.duration = duration/4

lineanimationfour.fillmode = kcafillmodeforwards

lineanimationfour.removedoncompletion = false

lineanimationfour.fromvalue = 0

lineanimationfour.tovalue = 1

for i in 0...3 {

if i == 3 {

lineanimationfour.delegate = self

}

let linelayer = lines[i]

linelayer.addanimation(lineanimationfour, forkey: "lineanimationfour")

}

}

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

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

iOS动画教你编写Slack的Loading动画进阶篇

?

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
//mark: public methods

/**

开始动画

*/

func startanimation() {

angleanimation()

lineanimationone()

lineanimationtwo()

lineanimationthree()

}

/**

暂停动画

*/

func pauseanimation() {

layer.pauseanimation()

for linelayer in lines {

linelayer.pauseanimation()

}

status = .pause

}

/**

继续动画

*/

func resumeanimation() {

layer.resumeanimation()

for linelayer in lines {

linelayer.resumeanimation()

}

status = .animating

}

extension calayer {

//暂停动画

func pauseanimation() {

// 将当前时间cacurrentmediatime转换为layer上的时间, 即将parent time转换为localtime

let pausetime = converttime(cacurrentmediatime(), fromlayer: nil)

// 设置layer的timeoffset, 在继续操作也会使用到

timeoffset = pausetime

// localtime与parenttime的比例为0, 意味着localtime暂停了

speed = 0;

}

//继续动画

func resumeanimation() {

let pausedtime = timeoffset

speed = 1

timeoffset = 0;

begintime = 0

// 计算暂停时间

let sincepause = converttime(cacurrentmediatime(), fromlayer: nil) - pausedtime

// local time相对于parent time时间的begintime

begintime = sincepause

}

}

//mark: animation delegate

override func animationdidstart(anim: caanimation) {

if let animation = anim as? cabasicanimation {

if animation.keypath == "transform.rotation.z" {

status = .animating

}

}

}

override func animationdidstop(anim: caanimation, finished flag: bool) {

if let animation = anim as? cabasicanimation {

if animation.keypath == "strokeend" {

if flag {

status = .normal

dispatch_after(dispatch_time(dispatch_time_now, int64(interval) * int64(nsec_per_sec)), dispatch_get_main_queue(), {

if self.status != .animating {

self.startanimation()

}

})

}

}

}

}

//mark: override

override func touchesended(touches: set<uitouch>, withevent event: uievent?) {

switch status {

case .animating:

pauseanimation()

case .pause:

resumeanimation()

case .normal:

startanimation()

}

}

总结

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持快网idc。

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

快网idc优惠网 建站教程 iOS动画教你编写Slack的Loading动画进阶篇 https://www.kuaiidc.com/91238.html

相关文章

发表评论
暂无评论