实用工具iOS高质量博客iOS Developer

【iOS】CALayer之CAEmitterLayer粒子发射器

2017-12-28  本文已影响253人  NotFunGuy

想必以前QQ空间的点赞效果大家都知道吧,点赞之后按钮周围会有一圈爆裂的小圆点;还有微信的红包雨表情动画等,以及烟花,火焰效果。这些看似很炫酷的动画可能让我们敬而远之,但是其实iOS封装的很好,利用简单的几行代码就能完成很炫酷的动画效果。由于目前正在玩儿iOS动画的内容,利用iOS的CAEmitterLayer结合CAEmitterCell能够达这些效果。不BB了,先上几个效果图。代码已传githubEmitterAnimation

红包雨 点赞效果

马丹 有没有办法一次性放很多个gif呀。。。。。。。


CAEmitterLayerCAEmitterCell

CAEmitterLayerCALayer的一个常用子类,CALayer的子类有很多,如果能很好的使用它们会得到一些意想不到的效果。CAEmitterLayer就是其中之一,CAEmitterLayer是用于实现基于Core Animation的粒子发生器系统。

`CALayer`的常用子类

所谓粒子,就是很多小颗粒,当让QQ空间的点赞动画的粒子也可以不用CAEmitterLayer粒子发生器来实现,不过这样会麻烦很多。在粒子系统中,CAEmitterLayer是用来发射粒子的,他所发射的粒子就是CAEmitterCell(当然粒子也可以发射粒子,也就是CAEmitterCell也可以发射CAEmitterCell)。可以认为CAEmitterLayerCAEmitterCell的工厂,通过不同的设置就会不断的产生想要的粒子。

原理其实很简单,但是动画就是这样,需要花时间去理解属性,只有很好的用它的属性,才能达到很炫酷的效果。查看API会发现CAEmitterLayerCAEmitterCell的属性都是很多的,并且有很多相同的属性。在CAEmitterLayer中,一些属性决定了粒子从什么样的几何特性上发射出来,这个几何特性包括了位置,形状,大小,并且还有一些渲染属性,用于一些渲染的效果。另外一些属性CAEmitterLayerCAEmitterCell都有的,在这里可能会迷糊,但是API说明的很清楚,CAEmitterLayer的这些属性会作为CAEmitterCell相同属性的系数,举个🌰,如果CAEmitterCellbirthRate = 10(每秒产生的粒子数量),其所属的CAEmitterLayerbirthRate = 2,那么在其他参数默认的情况下,这个CAEmitterCell总的每秒产生的粒子数量是10 * 2 = 20 。也就是每秒会产生20个这样的粒子。

另外,会发现CAEmitterCell的很多属性都带有一个Range,比如scaleRangevelocityRange,这些决定粒子自身的一些特性的属性大多都是以“中间值” + 范围(Range)的方式表示的。再举个🌰,比如scale = 0.5(缩放值)和scaleRange = 0.2(缩放的范围),那么表示的实际CAEmitterCell的缩放就是scale±scaleRange,即0.3~0.7这个范围。

红包雨demo

初步了解了这些之后,我们就可以跟着代码来实现实现一个红包雨的功能。实现起来很简单,只要设置好属性就行了,这些属性的详细含义会在下面的篇幅仔细讲解,先来试下一个小demo。就是最开始的第个效果图。

实现步骤
  1. 设置CAEmitterLayer以及它的一些模式,并添加到要显示的view的图层上,当然也可以替换view的图层。这里是直接添加到控制器的viewlayer上的,这个viewlayer是一个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这个属性,是将它放在了顶部,实现从天上掉下来的效果。

  1. 给这个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上

  1. 再添加一个按钮,通过用KVC设置CAEmitterLayerbirthRate来实现动画的开始和结束。
- (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"];
}
  1. run之后点击按钮就会出现上图的效果了。

CAEmitterLayerCAEmitterCell的属性详解

实现上面的小demo是不是很简单?不到20行代码而已,用到的属性和也很少。那么下雨下雪的效果也是这样实现的,只不过是修改属性值。但是这些效果不够炫酷,要实现炫酷的效果得先了解各个属性的含义,那么接下来花大量的篇幅讲解CAEmitterLayerCAEmitterCell的属性。

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里面的所有属性都已经贴出来并作了说明,看看注释并调试一下就能理解大部分,接下来重点说说一些常用的属性

1. CAEmitterLayer控制粒子发射位置和形状的属性

CAEmitterLayer发射的粒子并不是杂乱无章的,我们可以设置它发射粒子时的位置、几何图形等。通过以下属性去配置:

kCAEmitterLayerPoint
kCAEmitterLayerLine
kCAEmitterLayerRectangle
kCAEmitterLayerCuboid
kCAEmitterLayerCircle
kCAEmitterLayerSphere
  1. kCAEmitterLayerPoint:点形状,发射源的形状就是一个点,位置在上面position设置的位置
  2. kCAEmitterLayerLine:线形状,发射源的形状是一条线,位置在rect的横向的位于垂直方向中间那条
  3. kCAEmitterLayerRectangle:矩形状,发射源的形状是一个矩形,就是上面生成的那个矩形rect
  4. kCAEmitterLayerCuboid:立体矩形形状(3D),发射源是一个立体矩形,这里要生效的话需要设置z方向的数据,如果不设置就同矩形状
  5. kCAEmitterLayerCircle:圆形形状,发射源是一个圆形,形状为矩形包裹的那个圆,二维的
  6. kCAEmitterLayerSphere:立体圆形(3D),三维的圆形,同样需要设置z方向数据,不设置则通二维一样

这些形状可以在调试的时候修改来看看有什么不同,比如我们设置的红包效果,就是用的kCAEmitterLayerLine,结合了emitterSize来是实现从顶部掉下来的效果,而emitterSizeheight其实是被忽略的。接下来我们看如下代码的效果图解:

redpacketLayer.emitterPosition = CGPointMake(100, 100);
redpacketLayer.emitterSize = CGSizeMake(20, 0);
redpacketLayer.emitterShape = kCAEmitterLayerLine;
达到的效果
emitterShape的几种模式其实很好理解,以emitterPosition的点为中心,然后作一个对应的形状,如直线、圆形、矩形,在这个形状上产生相应的粒子。
kCAEmitterLayerPoints
kCAEmitterLayerOutline
kCAEmitterLayerSurface
kCAEmitterLayerVolume
  1. kCAEmitterLayerPoints:点模式,发射器是以点的形式发射粒子。发射点就是形状的某个特殊的点,比如shap是一个点的话,那么这个点就是中心点,如果是圆形,那么就是圆心。
  2. kCAEmitterLayerOutline:轮廓模式,从形状的边界上发射粒子。
  3. kCAEmitterLayerSurface:表面模式,从形状的表面上发射粒子。
  4. kCAEmitterLayerVolume:是相对于3D形状的“球体内”或“立方体内”发射,笔者暂时也不是很了解3D的。
2. CAEmitterLayer决定粒子系数的属性
3.CAEmitterLayer决定粒子内容的属性

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的帮助。

1.CAEmitterCell决定生命状态的属性
2.CAEmitterCell决定内容的属性
3.CAEmitterCell决定颜色状态的属性

粒子系统之所以能做出炫酷的效果,和它的色彩多样化有必不可上的关系,在CAEmitterCell中提供了较多的颜色控制属性这部分属性让你获得了控制粒子颜色,颜色变化范围和速度的能力,你可以凭借它来完成一些渐变的效果或其它构建在它之上的酷炫效果。接下来就看看这些颜色属性。

    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(00.2)、G(0.10.3)、B(0.20.4)、A(0.40.6)。

    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)。当然个变化的速度也可以负数,计算方式相同。比如要设置烟花的效果,那么要让在爆炸的过程中颜色变化,就是通过这样的设置达到的效果。

4.CAEmitterCell决定飞行轨迹的属性。

CAEmitterLayer虽然控制了粒子的发射位置和形状等,但是粒子的飞行同时也需要自身去决定,比如粒子发射的角度、发散的范围,自转属性等。那么接下来就说说这些属性。

    snowCell.emissionLongitude = M_PI_2;
    snowCell.emissionRange = M_PI_4;

实现的效果如下:


实现效果

可以看到粒子是沿着Y轴向下成90度的一个发散角度。如果想实现火焰等效果。就可以这样,把角度调小一点即可。

5.CAEmitterCell子粒子的属性

造几个小Demo

理解了CAEmitterLayerCAEmitterCell的属性之后,通过粒子系统实现一些炫酷的动画效果就很简单了。接下来对实现的几个小demo效果作个思路分享,欢迎提供更好的方法~。

QQ空间点赞动画

动画分析
实现思路
/**
 * 开始动画
 */
- (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];
}
/**
 * 选中状态 实现缩放
 */
- (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

上一篇下一篇

猜你喜欢

热点阅读