CAEmitterLayer 是一个高性能的粒子引擎,被用来创建复杂的粒子动画如:烟雾,火,雨等效果,并且很好地控制了性能。
苹果给出的解释是:
CAEmitterLayer 看上去像是许多 CAEmitterCell 的容器,这些 CAEmitterCell 定义了一个例子效果。你将会为不同的例子效果定义一个或多个 CAEmitterCell 作为模版,同时 CAEmitterLayer 负责基于这些模版实例化一个粒子流。一个 CAEmitterCell 类似于一个 CALayer :它有一个 contents 属性可以定义为一个 CGImage ,另外还有一些可设置属性控制着表现和行为。
以上解释来源于网络
首先提醒CAEmitterLayer本身没有什么难度,主要在于两点:
- 属性较多(一会会把属性都列举出来,不知道了随时查阅就是)
- 调参数比较费时(想要有好的动画效果还得慢慢的去调整各项参数,不过没有难度就是有点费时间)
下面先认识一下CAEmitterLayer的属性
/* The center of the emission shape. Defaults to (0, 0, 0). Animatable. */
发射源位置。注意,是一个空间坐标。并且标记为 Animatable. 也就是说可以用 CoreAnimation 移动发射源位置
@property CGPoint emitterPosition;
@property CGFloat emitterZPosition;
“/* The size of the emission shape. Defaults to (0, 0, 0). Animatable.
* Depending on the `emitterShape' property some of the values may be
* ignored. */
发射源大小。注意除了宽和高之外,还有纵向深度。
文档中还提到,这两个属性有时候可能会因为设置了 emitterShape 而被忽略,具体情况实际尝试一下就可以了。
@property CGSize emitterSize;
@property CGFloat emitterDepth;
“/* A string defining the type of emission shape used. Current options are:
* `point' (the default), `line', `rectangle', `circle', `cuboid' and
* `sphere'. */
CA_EXTERN NSString * const kCAEmitterLayerPoint
__OSX_AVAILABLE_STARTING (__MAC_10_6, __IPHONE_5_0);
CA_EXTERN NSString * const kCAEmitterLayerLine
__OSX_AVAILABLE_STARTING (__MAC_10_6, __IPHONE_5_0);
CA_EXTERN NSString * const kCAEmitterLayerRectangle
__OSX_AVAILABLE_STARTING (__MAC_10_6, __IPHONE_5_0);
CA_EXTERN NSString * const kCAEmitterLayerCuboid
__OSX_AVAILABLE_STARTING (__MAC_10_6, __IPHONE_5_0);
CA_EXTERN NSString * const kCAEmitterLayerCircle
__OSX_AVAILABLE_STARTING (__MAC_10_6, __IPHONE_5_0);
CA_EXTERN NSString * const kCAEmitterLayerSphere
__OSX_AVAILABLE_STARTING (__MAC_10_6, __IPHONE_5_0);
emitterShape 决定了发射源的形状。
@property(copy) NSString *emitterShape;
/* A string defining how particles are created relative to the emission
* shape. Current options are `points', `outline', `surface' and
* `volume' (the default). */
CA_EXTERN NSString * const kCAEmitterLayerPoints
__OSX_AVAILABLE_STARTING (__MAC_10_6, __IPHONE_5_0);
CA_EXTERN NSString * const kCAEmitterLayerOutline
__OSX_AVAILABLE_STARTING (__MAC_10_6, __IPHONE_5_0);
CA_EXTERN NSString * const kCAEmitterLayerSurface
__OSX_AVAILABLE_STARTING (__MAC_10_6, __IPHONE_5_0);
CA_EXTERN NSString * const kCAEmitterLayerVolume
__OSX_AVAILABLE_STARTING (__MAC_10_6, __IPHONE_5_0);
emitterMode 决定了发射源的发射模式。
@property(copy) NSString *emitterMode;
平常用的多的比如 emitterShape 的 kCAEmitterLayerLine 和 kCAEmitterLayerPoint。这两个从视觉上还是比较好区分的,这决定了你的粒子是从一个点「喷」出来的,还是从一条线上每个点「喷」下来,前者像焰火,后者像瀑布。显然,下雪的效果更像后者。
emitterMode 的 kCAEmitterLayerOutline 表示向外围扩散,如果你的发射源形状是 circle,那么 kCAEmitterLayerOutline 就会以一个圆的方式向外扩散开。
又比如你想表达一股蒸汽向上喷的效果,就可以设置 emitterShape 为 kCAEmitterLayerLine , emitterMode 为 kCAEmitterLayerOutline。
CAEmitterCell的属性
其实CAEmitterCell真是的名字叫粒子,下面详细的介绍了CAEmitterCell的属性,只要求大家属性一下,以后用到了可以再来查阅。
@property float birthRate; //每秒生成多少个粒子
@property float lifetime; //粒子存活的时间,以秒为单位
@property float lifetimeRange; // 可以为这个粒子存活的时间再指定一个范围。
上面两个属性如果只用了lifetime那么粒子的存活时间就是固定的,比如lifetime=10,那么粒子10s秒后就消失了。
如果使用了lifetimeRange,比如lifetimeRange=5,那么粒子的存活时间就是在5s~15s这个范围内消失。
@property CGFloat velocity;//粒子平均初始速度。正数表示竖直向上,负数竖直向下。
@property CGFloat velocityRange; //可以再指定一个范围。
上面两个属性同lifetime和lifetimeRange
@property CGFloat xAcceleration;
@property CGFloat yAcceleration;
@property CGFloat zAcceleration; //三者构成了一个空间矢量。决定了每个方向上粒子的加速度。
@property CGFloat emissionRange; //以锥形分布开的发射角度。角度用弧度制。粒子均匀分布在这个锥形范围内。
@property CGFloat spin;//粒子的平均旋转速度
@property CGFloat spinRange; //可指定一个范围。弧度制。
@property(strong) id contents; //cell的内容。通常是一个指针CGImageRef。
@property CGColorRef color; //可以把图片「染」成你想要的颜色。
@property(copy) NSString *name; //The name of the cell,用于构建key paths。这也是后面手动控制动画开始和结束的关键。
好,上面简单介绍了一下CAEmitterLayer和CAEmitterCell的一些基本属性,下面来利用粒子动画实现一个类似今日头条点赞效果。
#import
@interface GXUpvoteButton : UIButton
@end
#import "GXUpvoteButton.h"
@interface GXUpvoteButton()
/**
展示的layer
*/
@property (strong, nonatomic) CAEmitterLayer *streamerLayer;
/**
图片数组
*/
@property (nonatomic, strong) NSMutableArray *imagesArr;
/**
cell的数组
*/
@property (nonatomic, strong) NSMutableArray *CAEmitterCellArr;
/**
展示多少个赞的label
*/
@property (nonatomic, strong) UILabel *zanLabel;
@end
@implementation GXUpvoteButton
{
NSTimer *_timer; //定时器
NSInteger countNum;//赞的个数
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
[self setup];
}
return self;
}
/**
* 配置WclEmitterButton
*/
- (void)setup {
//初始化 赞的个数
countNum = 1;
//展示多少个赞的label
self.zanLabel = [[UILabel alloc]init];
[self addSubview:self.zanLabel];
self.zanLabel.frame = CGRectMake(-50 ,- 100, 200, 40);
self.zanLabel.hidden = YES;
//添加点击事件
//点一下
[self addGestureRecognizer:[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(pressOnece:)]];
//长按
[self addGestureRecognizer:[[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(longPress:)]];
[self setImage:[UIImage imageNamed:@"feed_like"] forState:UIControlStateNormal];
[self setImage:[UIImage imageNamed:@"feed_like_press"] forState:UIControlStateSelected];
//设置暂时的layer
_streamerLayer = [CAEmitterLayer layer];
_streamerLayer.emitterSize = CGSizeMake(30, 30);
_streamerLayer.masksToBounds = NO;
_streamerLayer.renderMode = kCAEmitterLayerAdditive;
[self.layer addSublayer:_streamerLayer];
}
/**
点了一下
@param ges 手势
*/
- (void)pressOnece:(UIGestureRecognizer *)ges
{
UIButton * sender = (UIButton *)ges.view;
sender.selected = !sender.selected;
[self animation];
[self performSelector:@selector(explode) withObject:nil afterDelay:0.1];
if (sender.selected == NO) {
//重置label文字
countNum = 0;
[self changeText];
//清空数组
[self.imagesArr removeAllObjects];
[self.CAEmitterCellArr removeAllObjects];
}
}
/**
长按
@param ges 手势
*/
- (void)longPress:(UIGestureRecognizer *)ges
{
UIButton * sender = (UIButton *)ges.view;
sender.selected = YES;
if (ges.state == UIGestureRecognizerStateBegan) {
[self animation];
}else if (ges.state == UIGestureRecognizerStateEnded)
{
[self explode];
}
}
/**
* 开始动画
*/
- (void)animation {
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];
if (self.selected) {
animation.values = @[@1.5 ,@0.8, @1.0,@1.2,@1.0];
animation.duration = 0.5;
[self startAnimate];
}else
{
animation.values = @[@0.8, @1.0];
animation.duration = 0.4;
}
animation.calculationMode = kCAAnimationCubic;
[self.layer addAnimation:animation forKey:@"transform.scale"];
}
/**
* 开始喷射
*/
- (void)startAnimate {
for (int i = 1; i < 10; i++)
{
//78张图片 随机选9张
int x = arc4random() % 77 + 1;
NSString * imageStr = [NSString stringWithFormat:@"emoji_%d",x];
[self.imagesArr addObject:imageStr];
}
//设置展示的cell
for (NSString * imageStr in self.imagesArr) {
CAEmitterCell * cell = [self emitterCell:[UIImage imageNamed:imageStr] Name:imageStr];
[self.CAEmitterCellArr addObject:cell];
}
_streamerLayer.emitterCells = self.CAEmitterCellArr;
// 开启计时器 设置点赞次数的label
self.zanLabel.hidden = NO;
_timer = [NSTimer scheduledTimerWithTimeInterval:0.15 target:self selector:@selector(changeText) userInfo:nil repeats:YES];
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];
animation.values = @[@0.8, @1.0];
animation.duration = 0.4;
[self.zanLabel.layer addAnimation:animation forKey:@"transform.scale"];
//_streamerLayer开始时间
_streamerLayer.beginTime = CACurrentMediaTime();
for (NSString * imgStr in self.imagesArr) {
NSString * keyPathStr = [NSString stringWithFormat:@"emitterCells.%@.birthRate",imgStr];
[_streamerLayer setValue:@7 forKeyPath:keyPathStr];
}
}
/**
* 停止喷射
*/
- (void)explode {
//让chareLayer每秒喷射的个数为0个
for (NSString * imgStr in self.imagesArr) {
NSString * keyPathStr = [NSString stringWithFormat:@"emitterCells.%@.birthRate",imgStr];
[self.streamerLayer setValue:@0 forKeyPath:keyPathStr];
}
_zanLabel.hidden = YES;
[_timer invalidate];
_timer = nil;
}
/**
更改点赞个数label的文字
*/
- (void)changeText
{
countNum ++;
self.zanLabel.attributedText = [self getAttributedString:countNum];
self.zanLabel.textAlignment = NSTextAlignmentCenter;
}
/**
富文本设置label的图片内容
@param num 当前赞的个数
@return 要显示的富文本
*/
- (NSMutableAttributedString *)getAttributedString:(NSInteger)num
{
//先把num 拆成个十百
NSInteger ge = num % 10;
NSInteger shi = num % 100 / 10;
NSInteger bai = num % 1000 / 100;
//大于1000则隐藏
if (num >= 1000) {
return nil;
}
NSMutableAttributedString * mutStr = [[NSMutableAttributedString alloc]init];
//创建百位显示的图片
if (bai != 0) {
NSTextAttachment *b_attch = [[NSTextAttachment alloc] init];
b_attch.image = [UIImage imageNamed:[NSString stringWithFormat:@"multi_digg_num_%ld",bai]];
b_attch.bounds = CGRectMake(0, 0, b_attch.image.size.width, b_attch.image.size.height);
NSAttributedString *b_string = [NSAttributedString attributedStringWithAttachment:b_attch];
[mutStr appendAttributedString:b_string];
}
//创建十位显示的图片
if (!(shi == 0 && bai == 0)) {
NSTextAttachment *s_attch = [[NSTextAttachment alloc] init];
s_attch.image = [UIImage imageNamed:[NSString stringWithFormat:@"multi_digg_num_%ld",shi ]];
s_attch.bounds = CGRectMake(0, 0, s_attch.image.size.width, s_attch.image.size.height);
NSAttributedString *s_string = [NSAttributedString attributedStringWithAttachment:s_attch];
[mutStr appendAttributedString:s_string];
}
//创建个位显示的图片
if (ge >= 0) {
NSTextAttachment *g_attch = [[NSTextAttachment alloc] init];
g_attch.image = [UIImage imageNamed:[NSString stringWithFormat:@"multi_digg_num_%ld",ge]];
g_attch.bounds = CGRectMake(0, 0, g_attch.image.size.width, g_attch.image.size.height);
NSAttributedString *g_string = [NSAttributedString attributedStringWithAttachment:g_attch];
[mutStr appendAttributedString:g_string];
}
if (num <= 3) {
//鼓励
NSTextAttachment *attch = [[NSTextAttachment alloc] init];
attch.image = [UIImage imageNamed:@"multi_digg_word_level_1"];
attch.bounds = CGRectMake(0, 0, attch.image.size.width, attch.image.size.height);
NSAttributedString *z_string = [NSAttributedString attributedStringWithAttachment:attch];
[mutStr appendAttributedString:z_string];
}else if (num <= 6)
{
//加油
NSTextAttachment *attch = [[NSTextAttachment alloc] init];
attch.image = [UIImage imageNamed:@"multi_digg_word_level_2"];
attch.bounds = CGRectMake(0, 0, attch.image.size.width, attch.image.size.height);
NSAttributedString *z_string = [NSAttributedString attributedStringWithAttachment:attch];
[mutStr appendAttributedString:z_string];
}else
{
//太棒了
NSTextAttachment *attch = [[NSTextAttachment alloc] init];
attch.image = [UIImage imageNamed:@"multi_digg_word_level_3"];
attch.bounds = CGRectMake(0, 0, attch.image.size.width, attch.image.size.height);
NSAttributedString *z_string = [NSAttributedString attributedStringWithAttachment:attch];
[mutStr appendAttributedString:z_string];
}
return mutStr;
}
/**
创建发射的表情cell
@param image 传入随机的图片
@param name 图片的名字
@return cell
*/
- (CAEmitterCell *)emitterCell:(UIImage *)image Name:(NSString *)name
{
CAEmitterCell * smoke = [CAEmitterCell emitterCell];
smoke.birthRate = 0;//每秒出现多少个粒子
smoke.lifetime = 2;// 粒子的存活时间
smoke.lifetimeRange = 2;
smoke.scale = 0.35;
smoke.alphaRange = 1;
smoke.alphaSpeed = -1.0;//消失范围
smoke.yAcceleration = 450;//可以有下落的效果
CGImageRef image2 = image.CGImage;
smoke.contents= (__bridge id _Nullable)(image2);
smoke.name = name; //设置这个 用来展示喷射动画 和隐藏
smoke.velocity = 450;//速度
smoke.velocityRange = 30;// 平均速度
smoke.emissionLongitude = 3 * M_PI / 2 ;
smoke.emissionRange = M_PI_2;//粒子的发散范围
smoke.spin = M_PI * 2; // 粒子的平均旋转速度
smoke.spinRange = M_PI * 2;// 粒子的旋转速度调整范围
return smoke;
}
- (void)layoutSubviews
{
[super layoutSubviews];
//设置发射点的位置
_streamerLayer.position = CGPointMake(self.frame.size.width/2.0, self.frame.size.height/2.0);
}
- (NSMutableArray *)imagesArr
{
if (_imagesArr == nil) {
_imagesArr = [NSMutableArray array];
}
return _imagesArr;
}
- (NSMutableArray *)CAEmitterCellArr
{
if (_CAEmitterCellArr == nil) {
_CAEmitterCellArr = [NSMutableArray array];
}
return _CAEmitterCellArr;
}
调用方法:
- (void)viewDidLoad {
[super viewDidLoad];
self.upvoteButton = [GXUpvoteButton buttonWithType:UIButtonTypeCustom];
[self.view addSubview:self.upvoteButton];
self.upvoteButton.frame = CGRectMake(0, 0, 50, 50);
self.upvoteButton.center = self.view.center;
}
具体demo稍后提供...