Core Animation一些Demo总结 (动态切换图片、大转盘、图片折叠、进度条等动画效果)
前一篇总结了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轴方向上的“前面”)。
代码:
#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: 方法,这个方法可以设置你所要监听事件的区域范围。
代码:
#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的用法相似:
属性代码:
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,特定需求可以微调这个值。
代码:
#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就是用了上面所说的实现的,代码:
#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,设置下延迟时间,效果就出来了。
代码:
#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总结 (动态切换图片、大转盘、图片折叠、进度条等动画效果),希望对大家以上帮助!