前一篇总结了core animation的一些基础知识,这一篇主要是core animation 的一些应用,涉及到cashapelayer、careplicatorlayer等图层的知识。
先看效果图:
1、切换图片:
2、彩票转盘
3、图片折叠
4、进度条旋转
5、粒子效果
一、切换图片
看起来很复杂的动画,通过少量的计算和编码就可以简单的实现。要做到这一步,必须是需要研究ios开发中的core animation和core graphics框架的。日常工作中,对于很多东西不求甚解,只是拿过来用,甚至都不研究、封装一下别人代码,这种做法是很不好的。我喜欢自己造轮子,轮子造多了,开发经验与思维也就提升上去了。
这个动画实现是比较简单的,利用了cabasicanimation、cakeyframeanimation和caanimationgroup。看似是两张图片各自有着自己不同的动画,实际不过是一个动画方法,其平移方向与旋转角度的不同。
我是用了cabasicanimation设置了view的zposition值,cakeyframeanimation对象设计了图片的位移与旋转动画,然后将之放到caanimationgroup对象里面,开始动画。
这里有一个注意点,那就是core animation设置的动画位移、旋转、缩放都只是一个假象,实际上的view该怎么还是怎么样,并未真正有过变化。所以,在动画结束后,想要正确的效果,那么需要设置view的zposition值,这个值越大,view越在前面(z轴方向上的“前面”)。
代码:
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
|
# import "viewcontroller.h" @interface viewcontroller () @property (nonatomic, strong) nsmutablearray *images; @property (nonatomic, assign) int currentindex; @property (weak, nonatomic) iboutlet uiimageview *currentimageview; @property (weak, nonatomic) iboutlet uiimageview *behindimageview; @end @implementation viewcontroller - (nsmutablearray *)images { if (_images == nil) { _images = [nsmutablearray array]; for ( int i = 1 ; i <= 7 ; i++) { uiimage *image = [uiimage imagenamed: [nsstring stringwithformat:@ "%d" ,i]]; [_images addobject:image]; } } return _images; } - ( void )viewdidload { [ super viewdidload]; self.currentindex = 0 ; self.currentimageview.image = self.images[_currentindex]; } - ( void )addanimatewithpoint:(cgpoint )point angle:(cgfloat)angle fromz:(cgfloat)fromz toz:(cgfloat)toz view:(uiview *)view { cabasicanimation *zposition = [[cabasicanimation alloc] init]; zposition.keypath = @ "zposition" ; zposition.fromvalue = @(fromz); zposition.tovalue = @(toz); zposition.duration = 1.2 ; cakeyframeanimation *rotation = [[cakeyframeanimation alloc] init]; rotation.keypath = @ "transform.rotation" ; rotation.values = @[@( 0 ), @(angle), @( 0 )]; rotation.duration = 2 ; rotation.timingfunctions = @[ [camediatimingfunction functionwithname:kcamediatimingfunctioneaseineaseout], [camediatimingfunction functionwithname:kcamediatimingfunctioneaseineaseout]]; cakeyframeanimation *position = [[cakeyframeanimation alloc] init]; position.keypath = @ "position" ; // cgpointmake(110, -20) position.values = @[ [nsvalue valuewithcgpoint:cgpointzero], [nsvalue valuewithcgpoint:point], [nsvalue valuewithcgpoint:cgpointzero] ]; position.timingfunctions = @[ [camediatimingfunction functionwithname:kcamediatimingfunctioneaseineaseout], [camediatimingfunction functionwithname:kcamediatimingfunctioneaseineaseout] ]; position.additive = yes; position.duration = 1.2 ; caanimationgroup *animategroup = [[caanimationgroup alloc] init]; animategroup.animations = @[zposition, rotation, position]; // animategroup.begintime = 0.5; animategroup.delegate = self; animategroup.duration = 1.2 ; [animategroup setvalue:view forkey:@ "view" ]; [view.layer addanimation:animategroup forkey:nil]; view.layer.zposition = toz; } - ( void )animationdidstop:(caanimation *)anim finished:(bool)flag { caanimationgroup *group = [anim valueforkey:@ "view" ]; if (group != nil) { self.currentimageview.image = self.images[_currentindex]; self.currentimageview.layer.zposition = 1 ; self.behindimageview.image = nil; self.behindimageview.layer.zposition = - 1 ; } } - (ibaction)previous:(id)sender { self.currentindex = (self.currentindex + 1 ) % self.images.count; self.behindimageview.image = self.images[_currentindex]; [self addanimatewithpoint:cgpointmake(- 90 , 20 ) angle: 0.15 fromz:- 1 toz: 1 view:self.behindimageview]; [self addanimatewithpoint:cgpointmake( 90 , - 20 ) angle:- 0.15 fromz: 1 toz:- 1 view:self.currentimageview]; } - (ibaction)next:(id)sender { self.currentindex = (self.currentindex + 6 ) % self.images.count; self.behindimageview.image = self.images[_currentindex]; [self addanimatewithpoint:cgpointmake(- 90 , 20 ) angle:- 0.15 fromz:- 1 toz: 1 view:self.behindimageview]; [self addanimatewithpoint:cgpointmake( 90 , - 20 ) angle: 0.15 fromz: 1 toz:- 1 view:self.currentimageview]; } @end |
二、彩票转盘
这个动画的实现主要难点在于button的摆放,只要摆放好button,其他就是简单的添加动画的一个过程。
12个星座,那么需要12个button。在摆放它们的时候,我是将一个个button的anchorpoint设置为(0.5, 1),将button的position设置为中心圆的圆心,然后设置transform来旋转它们,是的达到围绕一个圆摆放的目的。
需要知道的是,一般来说,控件的anchorpoint就是控件的中心点,所以在我们做旋转、平移等操作的时候,也就是在围绕中心点的一系列操作。但是,很多时候,只是围绕中心点来设置动画的话,会很复杂,calayer提供了一个anchorpoint属性,可以让我们自由的改变其数值,从而实现比较复杂的动画。
还有就是button的点击事件,事实上由于一个扇形区域是上边大,下边小,要是不做相应的限制,当用户点击下面的区域时,很可能不是选中当前的button,因此要做一定的限制。这里我是自定义了一个button,在里面重写了hittest: wwthevent: 方法,这个方法可以设置你所要监听事件的区域范围。
代码:
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
# import "zywheelview.h" #define zyimagew 40 #define zyimageh 46 @interface zybutton : uibutton @end @implementation zybutton /** * 重写此方法,截取button的点击 * */ - (uiview *)hittest:(cgpoint)point withevent:(uievent *)event { cgfloat btnw = self.bounds.size.width; cgfloat btnh = self.bounds.size.height; cgfloat x = 0 ; cgfloat y = btnh / 2 ; cgfloat w = btnw; cgfloat h = y; cgrect rect = cgrectmake(x, y, w, h); if (cgrectcontainspoint(rect, point)) { return nil; } else { return [ super hittest:point withevent:event]; } } - (cgrect)imagerectforcontentrect:(cgrect)contentrect { cgfloat imagex = (contentrect.size.width - zyimagew ) * 0.5 ; cgfloat imagey = 18 ; return cgrectmake(imagex, imagey, zyimagew, zyimageh); } - ( void )sethighlighted:(bool)highlighted { } @end @interface zywheelview () @property (weak, nonatomic) iboutlet uiimageview *wheelview; @property (nonatomic, weak) uibutton *lastselectedbtn; @property (nonatomic, strong) cadisplaylink *timer; @end @implementation zywheelview + (instancetype)wheelview { return [[[nsbundle mainbundle] loadnibnamed:@ "zywheelview" owner:nil options:nil] lastobject]; } - ( void )awakefromnib { self.wheelview.userinteractionenabled = yes; cgfloat angle = 2 * m_pi / 12.0 ; uiimage *normalimage = [uiimage imagenamed:@ "luckyastrology" ]; uiimage *selectedimage = [uiimage imagenamed:@ "luckyastrologypressed" ]; for ( int bi = 0 ; bi < 12 ; bi++) { zybutton *btn = [[zybutton alloc] init]; [btn setbackgroundimage:[uiimage imagenamed:@ "luckyrototeselected" ] forstate:uicontrolstateselected]; // 切割图片,将切割好的图片设置到按钮上 // cgimage中rect是当做像素来使用 // uikit 中是点坐标系 // 坐标系的特点:如果在非retain屏上 1个点等于1个像素 // 在retain屏上1个点等于2个像素 cgfloat imageh = zyimageh * [uiscreen mainscreen].scale; cgfloat imagew = zyimagew * [uiscreen mainscreen].scale; cgfloat imagey = 0 ; cgfloat imagex = bi * imagew; cgrect rect = cgrectmake(imagex, imagey, imagew, imageh); cgimageref normalref = cgimagecreatewithimageinrect(normalimage.cgimage, rect); cgimageref selectedref = cgimagecreatewithimageinrect(selectedimage.cgimage, rect); [btn setimage:[uiimage imagewithcgimage:normalref] forstate:uicontrolstatenormal]; [btn setimage:[uiimage imagewithcgimage:selectedref] forstate:uicontrolstateselected]; btn.bounds = cgrectmake( 0 , 0 , 58 , 143 ); btn.layer.anchorpoint = cgpointmake( 0.5 , 1 ); btn.layer.position = cgpointmake(self.frame.size.width * 0.5 , self.frame.size.height * 0.5 ); btn.transform = cgaffinetransformmakerotation(angle * bi); [btn addtarget:self action: @selector (clickbtn:) forcontrolevents:uicontroleventtouchupinside]; [self.wheelview addsubview:btn]; } [self startrotating]; } - ( void )startrotating { if (self.timer) return ; self.timer = [cadisplaylink displaylinkwithtarget:self selector: @selector (updatetimer)]; [self.timer addtorunloop:[nsrunloop mainrunloop] formode:nsrunloopcommonmodes]; } - ( void )stoprotating { [self.timer invalidate]; self.timer = nil; } - ( void )clickbtn:(uibutton *)btn { self.lastselectedbtn.selected = no; btn.selected = yes; self.lastselectedbtn = btn; } - (ibaction)clickcenterbtn:(id)sender { self.userinteractionenabled = no; [self stoprotating]; cabasicanimation *basicanimation = [cabasicanimation animationwithkeypath:@ "transform.rotation" ]; basicanimation.tovalue = @(m_pi * 2 * 5 ); basicanimation.duration = 2 ; basicanimation.timingfunction = [camediatimingfunction functionwithname:kcamediatimingfunctioneaseineaseout]; // basicanimation.removedoncompletion = no; // basicanimation.fillmode = kcafillmodeforwards; basicanimation.delegate = self; [self.wheelview.layer addanimation:basicanimation forkey:nil]; } - ( void )animationdidstop:(caanimation *)anim finished:(bool)flag { self.userinteractionenabled = yes; // 根据选中的按钮获取旋转的度数, // 通过transform获取角度 cgfloat angle = atan2(self.lastselectedbtn.transform.b, self.lastselectedbtn.transform.a); // 从实际上旋转转盘 self.wheelview.transform = cgaffinetransformmakerotation(-angle); dispatch_after(dispatch_time(dispatch_time_now, (int64_t)( 1 * nsec_per_sec)), dispatch_get_main_queue(), ^{ [self startrotating]; }); } - ( void )updatetimer { self.wheelview.transform = cgaffinetransformrotate(self.wheelview.transform, m_pi / 200 ); } - ( void )dealloc { [self stoprotating]; } @end |
3、图片折叠
这个效果看起来很炫酷,但实际做起来是比较简单的。需要三个view,两个uiimageview,一个接受拖拽action的view。calayer里面有个contentrect属性,它可以设置layer里面的显示内容,利用这个属性,我们可以做在下载图片时,下载一点展示一点的效果。 在这里,我用这个属性来这是两张uiimageview各自展示一半的图片,然后将这两张imageview拼接在一起,显示完整的图片。
在一个覆盖这张完整图片的view上添加拖拽手势,以实现动画过程。
这里有一个新的图层需要学习下,cagradientlayer,它是用来做颜色渐变的,用法与calayer的用法相似:
属性代码:
1
2
3
4
5
6
7
8
9
10
11
12
|
cagradientlayer *gradientlayer = [cagradientlayer layer]; gradientlayer.frame = self.bottomview.bounds; gradientlayer.opacity = 0 ; gradientlayer.colors = @[(id)[uicolor clearcolor].cgcolor, (id)[uicolor blackcolor].cgcolor]; self.gradientlayer = gradientlayer; [self.bottomview.layer addsublayer:gradientlayer]; // 设置渐变颜色 // gradientl.colors = @[(id)[uicolor redcolor].cgcolor,(id)[uicolor greencolor].cgcolor,(id)[uicolor yellowcolor].cgcolor]; // 设置渐变定位点 // gradientl.locations = @[@0.1,@0.4,@0.5]; // 设置渐变开始点,取值0~1 // gradientl.startpoint = cgpointmake(0, 1); |
设置好之后,在pan手势的方法里面不断改变gradientlayer的opacity即可达到想要的效果。
catransform3d有个m34属性,可以设置透视度,一般将这个值设置为- 1 / 500.0,特定需求可以微调这个值。
代码:
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
|
# import "viewcontroller.h" @interface viewcontroller () @property (weak, nonatomic) iboutlet uiimageview *topview; @property (weak, nonatomic) iboutlet uiimageview *bottomview; @property (weak, nonatomic) iboutlet uiview *containview; @property (nonatomic, weak) cagradientlayer *gradientlayer; @end @implementation viewcontroller - ( void )viewdidload { [ super viewdidload]; // do any additional setup after loading the view, typically from a nib. [self setupotherview]; //设置渐变的阴影 [self setupshadow]; } - ( void )setupotherview { //设置contentsrect用来表示图片显示的大小,可以做边下载边显示的ui效果,取值是(0--1) self.topview.layer.contentsrect = cgrectmake( 0 , 0 , 1 , 0.5 ); self.topview.layer.anchorpoint = cgpointmake( 0.5 , 1 ); self.bottomview.layer.contentsrect = cgrectmake( 0 , 0.5 , 1 , 0.5 ); self.bottomview.layer.anchorpoint = cgpointmake( 0.5 , 0 ); uipangesturerecognizer *gesture = [[uipangesturerecognizer alloc] initwithtarget:self action: @selector (pan:)]; [self.containview addgesturerecognizer:gesture]; } - ( void )setupshadow { cagradientlayer *gradientlayer = [cagradientlayer layer]; gradientlayer.frame = self.bottomview.bounds; gradientlayer.opacity = 0 ; gradientlayer.colors = @[(id)[uicolor clearcolor].cgcolor, (id)[uicolor blackcolor].cgcolor]; self.gradientlayer = gradientlayer; [self.bottomview.layer addsublayer:gradientlayer]; // 设置渐变颜色 // gradientl.colors = @[(id)[uicolor redcolor].cgcolor,(id)[uicolor greencolor].cgcolor,(id)[uicolor yellowcolor].cgcolor]; // 设置渐变定位点 // gradientl.locations = @[@0.1,@0.4,@0.5]; // 设置渐变开始点,取值0~1 // gradientl.startpoint = cgpointmake(0, 1); } - ( void )pan:(uipangesturerecognizer *)recognizer { cgfloat y = [recognizer translationinview:self.containview].y; if (y >= 300 ) y = 300 ; if (y <= - 300 ) y = - 300 ; // 旋转角度,往下逆时针旋转 cgfloat angle = -y / 320.0 * m_pi; self.topview.layer.transform = catransform3didentity; catransform3d transfrom = catransform3didentity; transfrom.m34 = - 1 / 500.0 ; self.topview.layer.transform = catransform3drotate(transfrom, angle, 1 , 0 , 0 ); self.gradientlayer.opacity = y / 300.0 ; if (recognizer.state == uigesturerecognizerstateended) { // 弹簧效果的动画 // springwithdamping:弹性系数,越小,弹簧效果越明显 [uiview animatewithduration: 0.5 delay: 0 usingspringwithdamping: 0.3 initialspringvelocity: 11 options:uiviewanimationoptioncurveeaseinout animations:^{ self.topview.layer.transform = catransform3didentity; self.gradientlayer.opacity = 0 ; } completion:nil]; } } @end |
4、旋转进度条
圆圈旋转一般都是放在hud上吧。记得以前我也做过一个类似的功能,那时候还没现在这样的知识储备,只能是用cakeyframeanimation做,让美工做出了一根顶部是一个小白点,除此之外,很长的那部分是为clearcolor的小矩形,然后我设置它的anchorpoint,给 cakeyframeanimation添加一个圆形的path,然后围绕这个path旋转,做是勉强做出来,但是很不好看吧。
现在可以有更好的选择了,careplicatorlayer(复制图层)。我们可以在复制图层里面添加一个instance图层,如果设置了复制图层的instancecount,假如让instancecount == 5, 那么复制图层会自动帮我们复制5个跟instance图层一样的图层(事实上,我们可以在一开始就给instance图层设置动画,那么在复制的时候,一样会把动画也复制过来),除此之外,还可以设置复制图层里面的instance图层的transfrom,从而实现一定的布局。复制图层里面还有一个instancedelay,它表示延迟多少时间开始动画等等。
这个demo就是用了上面所说的实现的,代码:
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
|
# import "viewcontroller.h" @interface viewcontroller () @property (weak, nonatomic) iboutlet uiview *containview; @end @implementation viewcontroller - ( void )viewdidload { [ super viewdidload]; // do any additional setup after loading the view, typically from a nib. [self setupreplicatorlayerandanimation]; } - ( void )setupreplicatorlayerandanimation { careplicatorlayer *replicatorlayer = [careplicatorlayer layer]; replicatorlayer.frame = self.containview.layer.bounds; [self.containview.layer addsublayer:replicatorlayer]; calayer *layer = [calayer layer]; layer.frame = cgrectmake(self.containview.frame.size.width * 0.5 , 20 , 16 , 16 ); layer.backgroundcolor = [uicolor redcolor].cgcolor; layer.cornerradius = layer.frame.size.width / 2 ; //这一句可以将初始过程移除掉 layer.transform = catransform3dmakescale( 0 , 0 , 0 ); [replicatorlayer addsublayer:layer]; replicatorlayer.instancecount = 22 ; cabasicanimation *basican = [cabasicanimation animationwithkeypath:@ "transform.scale" ]; basican.fromvalue = @1 ; basican.tovalue = @0 ; basican.duration = 1 ; basican.repeatcount = maxfloat; [layer addanimation:basican forkey:nil]; replicatorlayer.instancedelay = basican.duration / ( double )replicatorlayer.instancecount; replicatorlayer.instancetransform = catransform3dmakerotation( 2 * m_pi / replicatorlayer.instancecount, 0 , 0 , 1 ); } @end |
5、粒子效果
这个东西就是careplicatorlayer(复制图层)和core graphics的结合吧,我是采用uibezierpath来绘制线条,然后将绘制好的path赋值给小球的animation路径,然后将小球添加到复制图层,设置下instancecount,设置下延迟时间,效果就出来了。
代码:
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
|
# import "zydrawview.h" @interface zydrawview () @property (nonatomic, strong) uibezierpath *bezierpath; @property (nonatomic, weak) careplicatorlayer *replicatorlayer; @property (nonatomic, weak) calayer *norlayer; @end static int _count = 0 ; @implementation zydrawview - (uibezierpath *)bezierpath { if (_bezierpath == nil) { _bezierpath = [[uibezierpath alloc] init]; } return _bezierpath; } - ( void )awakefromnib { careplicatorlayer *replicatorlayer = [careplicatorlayer layer]; replicatorlayer.frame = self.bounds; [self.layer addsublayer:replicatorlayer]; calayer *layer = [calayer layer]; layer.frame = cgrectmake( 0 , - 200 , 10 , 10 ); layer.cornerradius = layer.frame.size.width * 0.5 ; layer.backgroundcolor = [uicolor redcolor].cgcolor; [replicatorlayer addsublayer:layer]; self.replicatorlayer = replicatorlayer; self.norlayer = layer; } - ( void )touchesbegan:(nsset<uitouch *> *)touches withevent:(uievent *)event { cgpoint curpoint = [[touches anyobject] locationinview:self]; [self.bezierpath movetopoint:curpoint]; } - ( void )touchesmoved:(nsset<uitouch *> *)touches withevent:(uievent *)event { _count++; cgpoint curpoint = [[touches anyobject] locationinview:self]; [self.bezierpath addlinetopoint:curpoint]; [self setneedsdisplay]; } - ( void )startanimation { cakeyframeanimation *keyframean = [cakeyframeanimation animationwithkeypath:@ "position" ]; keyframean.path = self.bezierpath.cgpath; keyframean.duration = 4 ; keyframean.repeatcount = maxfloat; [self.norlayer addanimation:keyframean forkey:nil]; self.replicatorlayer.instancecount = _count; self.replicatorlayer.instancedelay = 0.1 ; } - ( void )redraw { _bezierpath = nil; _count = 1 ; [self.norlayer removeallanimations]; [self setneedsdisplay]; } - ( void )drawrect:(cgrect)rect { [self.bezierpath stroke]; } @end |
以上内容是小编给大家介绍的core animation一些demo总结 (动态切换图片、大转盘、图片折叠、进度条等动画效果),希望对大家以上帮助!