想必之前QQ空间的点赞效果你们都知道吧,点赞以后按钮周围会有一圈爆裂的小圆点;还有微信的红包雨表情动画等,以及烟花,火焰效果。这些看似很炫酷的动画可能让咱们敬而远之,可是其实iOS封装的很好,利用简单的几行代码就能完成很炫酷的动画效果。因为目前正在玩儿iOS动画的内容,利用iOS的CAEmitterLayer结合CAEmitterCell可以达这些效果。不BB了,先上几个效果图。代码已传github EmitterAnimation。
马丹 有没有办法一次性放不少个gif呀。。。。。。。git
CAEmitterLayer
与CAEmitterCell
CAEmitterLayer
是CALayer
的一个经常使用子类,CALayer
的子类有不少,若是能很好的使用它们会获得一些意想不到的效果。CAEmitterLayer
就是其中之一,CAEmitterLayer
是用于实现基于Core Animation
的粒子发生器系统。github
所谓粒子,就是不少小颗粒,当让QQ空间的点赞动画的粒子也能够不用CAEmitterLayer
粒子发生器来实现,不过这样会麻烦不少。在粒子系统中,CAEmitterLayer
是用来发射粒子的,他所发射的粒子就是CAEmitterCell
(固然粒子也能够发射粒子,也就是CAEmitterCell也能够发射CAEmitterCel
l)。能够认为CAEmitterLayer
是CAEmitterCell
的工厂,经过不一样的设置就会不断的产生想要的粒子。数组
原理其实很简单,可是动画就是这样,须要花时间去理解属性,只有很好的用它的属性,才能达到很炫酷的效果。查看API会发现CAEmitterLayer
和CAEmitterCell
的属性都是不少的,而且有不少相同的属性。在CAEmitterLayer
中,一些属性决定了粒子从什么样的几何特性上发射出来,这个几何特性包括了位置,形状,大小,而且还有一些渲染属性,用于一些渲染的效果。另一些属性CAEmitterLayer
和CAEmitterCell
都有的,在这里可能会迷糊,可是API说明的很清楚,CAEmitterLayer的这些属性会做为CAEmitterCell
相同属性的系数,举个?,若是CAEmitterCell
的birthRate = 10
(每秒产生的粒子数量),其所属的CAEmitterLayer
的birthRate = 2
,那么在其余参数默认的状况下,这个CAEmitterCell总的每秒产生的粒子数量是10 * 2 = 20 。也就是每秒会产生20个这样的粒子。微信
另外,会发现CAEmitterCell
的不少属性都带有一个Range
,好比scaleRange
、velocityRange
,这些决定粒子自身的一些特性的属性大多都是以“中间值” + 范围(Range)的方式表示的。再举个?,好比scale
= 0.5(缩放值)和scaleRange
= 0.2(缩放的范围),那么表示的实际CAEmitterCell
的缩放就是scale
±scaleRange
,即0.3~0.7这个范围。post
初步了解了这些以后,咱们就能够跟着代码来实现实现一个红包雨的功能。实现起来很简单,只要设置好属性就好了,这些属性的详细含义会在下面的篇幅仔细讲解,先来试下一个小demo。就是最开始的第个效果图。测试
CAEmitterLayer
以及它的一些模式,并添加到要显示的view的图层上,固然也能够替换view
的图层CAEmitterLayer
配置CAEmitterCell
。CAEmitterLayer
以及它的一些模式,并添加到要显示的view
的图层上,固然也能够替换view
的图层。这里是直接添加到控制器的view
的layer
上的,这个view
的layer
是一个calyer
类型的,若是想替换掉calyer
,能够自定义view
,并在view
里面重写以下方法便可实现替换// 替换view的layer + (Class)layerClass{return [CAEmitterLayer class];}
这里并无替换,而是直接添加。固然为了后面方便这里把CAEmitterLayer
设置为属性。详细代码以下,能够看注释动画
@interface ViewController () @property (nonatomic, strong) CAEmitterLayer * redpacketLayer; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [self redpacketRain]; } /** * 红包雨 */ - (void)redpacketRain{ // 1. 设置CAEmitterLayer CAEmitterLayer * redpacketLayer = [CAEmitterLayer layer]; [self.view.layer addSublayer:redpacketLayer]; self.redpacketLayer = redpacketLayer; redpacketLayer.emitterShape = kCAEmitterLayerLine; // 发射源的形状 是枚举类型 redpacketLayer.emitterMode = kCAEmitterLayerSurface; // 发射模式 枚举类型 redpacketLayer.emitterSize = self.view.frame.size; // 发射源的size 决定了发射源的大小 redpacketLayer.emitterPosition = CGPointMake(self.view.bounds.size.width * 0.5, -10); // 发射源的位置 redpacketLayer.birthRate = 0.f; // 每秒产生的粒子数量的系数 }
到这里CAEmitterLayer
的就设置完了,须要注意的是发射源的emitterPosition
这个属性,是将它放在了顶部,实现从天上掉下来的效果。atom
CAEmitterLayer
配置CAEmitterCell
。在redpacketRain
方法里面添加以下代码。值得注意的是,粒子的内容contents
是一个CGImageRef
类型的,用UIImage
须要转换为CGImage
.// 2. 配置cell CAEmitterCell * snowCell = [CAEmitterCell emitterCell]; snowCell.contents = (id)[[UIImage imageNamed:@"red_paceket"] CGImage]; // 粒子的内容 是CGImageRef类型的 snowCell.birthRate = 10.f; // 每秒产生的粒子数量 snowCell.lifetime = 20.f; // 粒子的生命周期 snowCell.velocity = 8.f; // 粒子的速度 snowCell.yAcceleration = 1000.f; // 粒子再y方向的加速的 snowCell.scale = 0.5; // 粒子的缩放比例 redpacketLayer.emitterCells = @[snowCell]; // 粒子添加到CAEmitterLayer上
CAEmitterLayer
的birthRate
来实现动画的开始和结束。- (IBAction)redpacketClick:(id)sender { [self.redpacketLayer setValue:@1.f forKeyPath:@"birthRate"]; [self performSelector:@selector(endRedpacketAnimation) withObject:nil afterDelay:2.f]; } - (void)endRedpacketAnimation{ [self.redpacketLayer setValue:@0.f forKeyPath:@"birthRate"]; }
CAEmitterLayer
与CAEmitterCell
的属性详解实现上面的小demo是否是很简单?不到20行代码而已,用到的属性和也不多。那么下雨下雪的效果也是这样实现的,只不过是修改属性值。可是这些效果不够炫酷,要实现炫酷的效果得先了解各个属性的含义,那么接下来花大量的篇幅讲解CAEmitterLayer
与CAEmitterCell
的属性。spa
CAEmitterLayer
经常使用属性@property(nullable, copy) NSArray<CAEmitterCell *> *emitterCells; // 用来装粒子的数组 @property float birthRate; // 粒子产生系数,默认1.0 @property float lifetime; // 粒子的生命周期系数, 默认1.0 @property CGPoint emitterPosition; // 决定了粒子发射形状的中心点 @property CGFloat emitterZPosition; @property CGSize emitterSize; // 发射源的尺寸大小 @property CGFloat emitterDepth; @property(copy) NSString *emitterShape; // 发射源的形状 @property(copy) NSString *emitterMode; // 发射模式 @property(copy) NSString *renderMode; // 渲染模式 @property BOOL preservesDepth; @property float velocity; // 粒子速度系数, 默认1.0 @property float scale; // 粒子的缩放比例系数, 默认1.0 @property float spin; // 粒子的自旋转速度系数, 默认1.0 @property unsigned int seed; // 随机数发生器
CAEmitterLayer
里面的API里面的全部属性都已经贴出来并做了说明,看看注释并调试一下就能理解大部分,接下来重点说说一些经常使用的属性调试
CAEmitterLayer
控制粒子发射位置和形状的属性CAEmitterLayer发射的粒子并非杂乱无章的,咱们能够设置它发射粒子时的位置、几何图形等。经过如下属性去配置:
emitterPosition
:决定发射源的中心点,若是好比上面的demo中设置的为CGPointMake(self.view.bounds.size.width * 0.5, -10)
,那么x轴方向上就是在view的中心点,而后再设置emitterSize = CGSizeMake(40, 0);
的话,那么就是self.view.bounds.size.width * 0.5
的左右两边各20。API中还提供了一个emitterZPosition
,这个是用在三维坐标中的,笔者暂时对三维的没有研究。emitterSize
: 决定发射源的大小emitterShape
:表示粒子从什么形状发射出来,它并非表示粒子本身的形状。是一个枚举类型,提供以下类型可供选择:kCAEmitterLayerPoint kCAEmitterLayerLine kCAEmitterLayerRectangle kCAEmitterLayerCuboid kCAEmitterLayerCircle kCAEmitterLayerSphere
kCAEmitterLayerPoint
:点形状,发射源的形状就是一个点,位置在上面position设置的位置kCAEmitterLayerLine
:线形状,发射源的形状是一条线,位置在rect的横向的位于垂直方向中间那条kCAEmitterLayerRectangle
:矩形状,发射源的形状是一个矩形,就是上面生成的那个矩形rectkCAEmitterLayerCuboid
:立体矩形形状(3D),发射源是一个立体矩形,这里要生效的话须要设置z方向的数据,若是不设置就同矩形状kCAEmitterLayerCircle
:圆形形状,发射源是一个圆形,形状为矩形包裹的那个圆,二维的kCAEmitterLayerSphere
:立体圆形(3D),三维的圆形,一样须要设置z方向数据,不设置则通二维同样这些形状能够在调试的时候修改来看看有什么不一样,好比咱们设置的红包效果,就是用的kCAEmitterLayerLine
,结合了emitterSize
来是实现从顶部掉下来的效果,而emitterSize
的height
实际上是被忽略的。接下来咱们看以下代码的效果图解:
redpacketLayer.emitterPosition = CGPointMake(100, 100); redpacketLayer.emitterSize = CGSizeMake(20, 0); redpacketLayer.emitterShape = kCAEmitterLayerLine;
emitterShape
的几种模式其实很好理解,以emitterPosition
的点为中心,而后做一个对应的形状,如直线、圆形、矩形,在这个形状上产生相应的粒子。
emitterMode
:发射模式,这个字段规定了在特定形状上发射的具体形式是什么。它的做用其实就是进一步决定发射的区域是在发射形状的哪一部份。kCAEmitterLayerPoints kCAEmitterLayerOutline kCAEmitterLayerSurface kCAEmitterLayerVolume
kCAEmitterLayerPoints
:点模式,发射器是以点的形式发射粒子。发射点就是形状的某个特殊的点,好比shap是一个点的话,那么这个点就是中心点,若是是圆形,那么就是圆心。kCAEmitterLayerOutline
:轮廓模式,从形状的边界上发射粒子。kCAEmitterLayerSurface
:表面模式,从形状的表面上发射粒子。kCAEmitterLayerVolume
:是相对于3D形状的“球体内”或“立方体内”发射,笔者暂时也不是很了解3D的。CAEmitterLayer
决定粒子系数的属性birthRate
: 粒子产生系数,默认1.0;每一个粒子cell
的产生率乘以这个粒子产生系数,得出每一秒产生这个粒子的个数。 即:每秒粒子产生个数 = layer.birthRate * cell.birthRate ;lifetime
:粒子的生命周期系数,默认1.0。计算方式同上;velocity
:粒子速度系数, 默认1.0。计算方式同上;scale
:粒子的缩放比例系数, 默认1.0。计算方式同上;spin
:自旋转速度系数, 默认1.0。计算方式同上;emitterCells
:用来装粒子的数组。每种粒子就是一个CAEmitterCell
。在API中能够看到CAEmitterCell
是服从CAMediatiming
协议的,能够经过beginTime
来控制subCell的出现时机。CAEmitterCell
经常使用属性@property(nullable, copy) NSString *name; // 粒子名字, 默认为nil @property(getter=isEnabled) BOOL enabled; @property float birthRate; // 粒子的产生率,默认0 @property float lifetime; // 粒子的生命周期,以秒为单位。默认0 @property float lifetimeRange; // 粒子的生命周期的范围,以秒为单位。默认0 @property CGFloat emissionLatitude;// 指定纬度,纬度角表明了在x-z轴平面坐标系中与x轴之间的夹角,默认0: @property CGFloat emissionLongitude; // 指定经度,经度角表明了在x-y轴平面坐标系中与x轴之间的夹角,默认0: @property CGFloat emissionRange; //发射角度范围,默认0,以锥形分布开的发射角度。角度用弧度制。粒子均匀分布在这个锥形范围内; @property CGFloat velocity; // 速度和速度范围,二者默认0 @property CGFloat velocityRange; @property CGFloat xAcceleration; // x,y,z方向上的加速度份量,三者默认都是0 @property CGFloat yAcceleration; @property CGFloat zAcceleration; @property CGFloat scale; // 缩放比例, 默认是1 @property CGFloat scaleRange; // 缩放比例范围,默认是0 @property CGFloat scaleSpeed; // 在生命周期内的缩放速度,默认是0 @property CGFloat spin; // 粒子的平均旋转速度,默认是0 @property CGFloat spinRange; // 自旋转角度范围,弧度制,默认是0 @property(nullable) CGColorRef color; // 粒子的颜色,默认白色 @property float redRange; // 粒子颜色red,green,blue,alpha能改变的范围,默认0 @property float greenRange; @property float blueRange; @property float alphaRange; @property float redSpeed; // 粒子颜色red,green,blue,alpha在生命周期内的改变速度,默认都是0 @property float greenSpeed; @property float blueSpeed; @property float alphaSpeed; @property(nullable, strong) id contents; // 粒子的内容,为CGImageRef的对象 @property CGRect contentsRect; @property CGFloat contentsScale; @property(copy) NSString *minificationFilter; @property(copy) NSString *magnificationFilter; @property float minificationFilterBias; @property(nullable, copy) NSArray<CAEmitterCell *> *emitterCells; // 粒子里面的粒子 @property(nullable, copy) NSDictionary *style;
CAEmitterCell
里面的API里面的大部分属性做了说明,看看注释并调试一下就能理解大部分,接下来重点说说一些经常使用的属性。CAEmitterLayer
就是粒子的工厂,可是要实现效果就须要CAEmitterCell
的帮助。
CAEmitterCell
决定生命状态的属性lifetime
、lifetimeRange
:粒子在系统上的生命周期,即存活时间,单位是秒。配合lifetimeRage
来让粒子生命周期均匀变化,以即可以让粒子的出现和消失显得更加离散。birthRate
:每秒钟产生的粒子的数量,是浮点数。对于这个数量为浮点数,在测试的时候能够灵活使用它。好比你想看粒子的运动状态,可是太多了可能会很迷糊,这时候你把birthRate = 0.1f
,其余参数不变,就能看到单个粒子的运动状态。 CAEmitterCell
决定内容的属性contents
:为CGImageRef
的对象。关于contents
会联想到CALayer
了,在CALayer
中展现静态的图片是须要用到这个属性。提供一张图片,做为粒子系统的粒子。可是由于粒子系统能够给粒子上色,为了作出好的颜色变换效果,一般提供的图片为纯色的图片,通常为白色。name
:粒子的名字。初看没什么用,可是当CAEmitterLayer
里面有不少个cell的时候,给每一个cell设置好名字,要修改一些属性以达到动画效果的改变等,就能够经过KVC拿到这个cell的某个属性。在后面的几个demo中都用用到。CAEmitterCell
决定颜色状态的属性
粒子系统之因此能作出炫酷的效果,和它的色彩多样化有必不可上的关系,在
CAEmitterCell
中提供了较多的颜色控制属性这部分属性让你得到了控制粒子颜色,颜色变化范围和速度的能力,你能够凭借它来完成一些渐变的效果或其它构建在它之上的酷炫效果。接下来就看看这些颜色属性。
color
:color
是粒子的颜色属性,这个颜色属性的做用是给粒子上色,它的实现方法很简单,就是将contents
自身的颜色的RGBA值 * color
的RGBA值,就获得最终的粒子的颜色。为了很好的计算,一般用白色的图片做为contents
,由于它的RGB都是255,转换为UIColor
中的component
就是1,用color乘上它就能获得color
设置的颜色效果。redRange
、greenRange
、blueRange
、alphaRange
:这些是对应的color的RGBA的取值范围,取值范围为0~1,好比以下设置中snowCell.color = [[UIColor colorWithRed:0.1 green:0.2 blue:0.3 alpha:0.5]CGColor]; snowCell.redRange = 0.1; snowCell.greenRange = 0.1; snowCell.blueRange = 0.1; snowCell.alphaRange = 0.1;
对应的RGBA的取值范围就是:R(0~0.2)、G(0.1~0.3)、B(0.2~0.4)、A(0.4~0.6)。
redSpeed
、greenSpeed
、blueSpeed
、alphaSpeed
:这些是对应的是粒子的RGBA的变化速度,取值范围为0~1。表示每秒钟的RGBA的变化率。这个变化率的计算方式其实很简单,先看下面的几行代码:snowCell.lifetime = 20.f; // 粒子的生命周期 snowCell.color = [[UIColor colorWithRed:0.f green:1.f blue:1.f alpha:1.f]CGColor]; snowCell.redSpeed = 0.2;
这里设置了粒子颜色的RGBA,以及redSpeed
,其余的没设置默认为0。粒子的生命周期(lifetime
)为20秒,那么这个粒子从产生到消失的时间就是20秒。它的Red值为0,redSpeed
为0.2,那么在粒子的这个生命周期内,粒子的每秒钟的Rde值就会增长 0.2 * 255
,表如今外观上的状态就是粒子颜色在不断变化,接近白色。最后粒子生命周期结束的时候,粒子的color
正好是RGBA(1,1,1,1)。固然个变化的速度也能够负数,计算方式相同。好比要设置烟花的效果,那么要让在爆炸的过程当中颜色变化,就是经过这样的设置达到的效果。
CAEmitterCell
决定飞行轨迹的属性。
CAEmitterLayer
虽然控制了粒子的发射位置和形状等,可是粒子的飞行同时也须要自身去决定,好比粒子发射的角度、发散的范围,自转属性等。那么接下来就说说这些属性。
emissionLongitude
: 指定经度,经度角表明了在x-y轴平面坐标系中与x轴之间的夹角,默认0,弧度制。顺时针方向为正。这样解释看起来很差懂,画个图就明白了。
粒子沿着X轴向右飞行,若是emissionLongtitude = 0
那么粒子会沿着X轴向右飞行,若是想沿着Y轴向下飞行,那么能够设置emissionLongtitude = M_PI_2
。
emissionLatitude
:这个和emissionLongitude
的原理是同样的,只不过是在三维平面上的x-z轴上与x轴的夹角。emissionRange
:发射角度范围,默认0,以锥形分布开的发射角度。角度用弧度制。粒子均匀分布在这个锥形范围内。在二维平面中,若想要以锥形的形式发射粒子,然粒子的发散范围不是一条线,而是一个锥形区域(也能够叫作扇形),那么能够经过emissionRange
来设置一个范围。好比想沿Y轴向下成90度的锥形区域发散,那么能够经过以下代码设置:snowCell.emissionLongitude = M_PI_2; snowCell.emissionRange = M_PI_4;
实现的效果以下:
能够看到粒子是沿着Y轴向下成90度的一个发散角度。若是想实现火焰等效果。就能够这样,把角度调小一点便可。
velocity
、velocityRange
、xAcceleration
、yAcceleration
、 zAcceleration
:前面两个是粒子的初速度和初速度的范围,后面是三个分别是在x、y、z轴方向的加速度,这个很好理解,初中就应该知道加速度的概念,也就是每秒钟速度的变化量。在放烟花的效果中,烟花飞上天的过程当中,模拟一个收重力影响的效果,就能够经过yAcceleration
模拟一个重力加速度的效果。spin
,spinRange
:这两个属性很重要,是粒子的自转属性。在粒子被发射出去以后,想要实现自转,就须要用到他们。粒子的自转是以弧度制来计算的,表示每秒钟粒子自转的弧度数。spin为正数表明粒子是顺时针旋转的,为负数的话就是逆时针选转了。举个?:粒子的生命周期就是20秒,那么你想让你的粒子这个生命周期内恰好自转12周,若spinRange
为0,那么粒子的spin
值就应该为((PI/180)360 2)/20,就获得了每秒须要转动的弧度数。CAEmitterCell
子粒子的属性emitterCells
:看到CAEmitterCell
的这个属性的时候或许会有些疑惑,不用惊讶,前面说过CAEmitterLayer
能够产生cell,通用cell也能够产生cell。那么这个属性就和CAEmitterLayer
中的emitterCells
同样,也是一个数组。这里有几个须要注意的地方:
emissionLongtitude
和emissionLatitude
这两个属性的状况。kCAEmitterLayerPoint
形状上由父粒子的中心发射出来的。理解了CAEmitterLayer
与CAEmitterCell
的属性以后,经过粒子系统实现一些炫酷的动画效果就很简单了。接下来对实现的几个小demo效果做个思路分享,欢迎提供更好的方法~。
CAKeyframeAnimation
实现setHighlighted
方法去掉高亮状态。提供两张图片,用于默认状态和选中状态。CAEmitterLayer
和粒子CAEmitterCell
。因为CAEmitterLayer
的birthRate
默认为1,CAEmitterCell
的birthRate
默认为0,那么先不设置这两个属性。给cell设置好name
,而后自定义一个动画开始的方法,在这里面经过KVC设置CAEmitterCell
的birthRate
以实现动画/** * 开始动画 */ - (void)startAnimation{ // 用KVC设置颗粒个数 [self.explosionLayer setValue:@1000 forKeyPath:@"emitterCells.explosionCell.birthRate"]; // 开始动画 self.explosionLayer.beginTime = CACurrentMediaTime(); // 延迟中止动画 [self performSelector:@selector(stopAnimation) withObject:nil afterDelay:0.15]; } /** * 动画结束 */ - (void)stopAnimation{ // 用KVC设置颗粒个数 [self.explosionLayer setValue:@0 forKeyPath:@"emitterCells.explosionCell.birthRate"]; [self.explosionLayer removeAllAnimations]; }
setSelected
方法,在里面经过关键帧动画实现缩放。/** * 选中状态 实现缩放 */ - (void)setSelected:(BOOL)selected{ [super setSelected:selected]; // 经过关键帧动画实现缩放 CAKeyframeAnimation * animation = [CAKeyframeAnimation animation]; animation.keyPath = @"transform.scale"; if (selected) { // 从没有点击到点击状态 会有爆炸的动画效果 animation.values = @[@1.5,@2.0, @0.8, @1.0]; animation.duration = 0.5; animation.calculationMode = kCAAnimationCubic; [self.layer addAnimation:animation forKey:nil]; // 让放大动画先执行完毕 再执行爆炸动画 [self performSelector:@selector(startAnimation) withObject:nil afterDelay:0.25]; }else{ // 从点击状态normal状态 无动画效果 若是点赞以后立刻取消 那么也立马中止动画 [self stopAnimation]; } }
还有几个动画的思路就不啰嗦了,实现起来都很简单,关键是要去理解这些属性的做用。另外动画作起来即便费时间,要本身去理解属性的做用的话须要花时间调试对比,其实用其余方式也能够实现这样的效果,可是CAEmitterLayer
基于GPU,作这些效果的时候比较方便。demo已经上传github了,提供了下雨、下雪、红包雨、五彩小球、爱心、火焰、烟花等效果,值得一说的是,烟花须要三个cell,一个提供发射的shootCell
,一个用于爆炸的explodeCell
,还有一个用于火花的sparkCell
,这些都是父子关系,一个cell生命周期完了另一个再出来。这些能够查看代码体会,都有注释~。EmitterAnimation