CAEmitterLayer 是一個(gè)高性能的粒子引擎始赎,被用來(lái)創(chuàng)建復(fù)雜的粒子動(dòng)畫如:煙霧,火,雨等效果钓试,并且很好地控制了性能。
蘋果給出的解釋是:
CAEmitterLayer 看上去像是許多 CAEmitterCell 的容器副瀑,這些 CAEmitterCell 定義了一個(gè)例子效果弓熏。你將會(huì)為不同的例子效果定義一個(gè)或多個(gè) CAEmitterCell 作為模版,同時(shí) CAEmitterLayer 負(fù)責(zé)基于這些模版實(shí)例化一個(gè)粒子流糠睡。一個(gè) CAEmitterCell 類似于一個(gè) CALayer :它有一個(gè) contents 屬性可以定義為一個(gè) CGImage 挽鞠,另外還有一些可設(shè)置屬性控制著表現(xiàn)和行為。
以上解釋來(lái)源于網(wǎng)絡(luò)
首先提醒CAEmitterLayer本身沒(méi)有什么難度,主要在于兩點(diǎn):
- 屬性較多(一會(huì)會(huì)把屬性都列舉出來(lái)信认,不知道了隨時(shí)查閱就是)
- 調(diào)參數(shù)比較費(fèi)時(shí)(想要有好的動(dòng)畫效果還得慢慢的去調(diào)整各項(xiàng)參數(shù)材义,不過(guò)沒(méi)有難度就是有點(diǎn)費(fèi)時(shí)間)
下面先認(rèn)識(shí)一下CAEmitterLayer的屬性
/* The center of the emission shape. Defaults to (0, 0, 0). Animatable. */
發(fā)射源位置。注意嫁赏,是一個(gè)空間坐標(biāo)其掂。并且標(biāo)記為 Animatable. 也就是說(shuō)可以用 CoreAnimation 移動(dòng)發(fā)射源位置
@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. */
發(fā)射源大小。注意除了寬和高之外潦蝇,還有縱向深度款熬。
文檔中還提到,這兩個(gè)屬性有時(shí)候可能會(huì)因?yàn)樵O(shè)置了 emitterShape 而被忽略攘乒,具體情況實(shí)際嘗試一下就可以了贤牛。
@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 決定了發(fā)射源的形狀。
@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 決定了發(fā)射源的發(fā)射模式持灰。
@property(copy) NSString *emitterMode;
平常用的多的比如 emitterShape 的 kCAEmitterLayerLine 和 kCAEmitterLayerPoint盔夜。這兩個(gè)從視覺(jué)上還是比較好區(qū)分的,這決定了你的粒子是從一個(gè)點(diǎn)「噴」出來(lái)的堤魁,還是從一條線上每個(gè)點(diǎn)「噴」下來(lái)喂链,前者像焰火,后者像瀑布妥泉。顯然椭微,下雪的效果更像后者。
emitterMode 的 kCAEmitterLayerOutline 表示向外圍擴(kuò)散盲链,如果你的發(fā)射源形狀是 circle蝇率,那么 kCAEmitterLayerOutline 就會(huì)以一個(gè)圓的方式向外擴(kuò)散開(kāi)。
又比如你想表達(dá)一股蒸汽向上噴的效果刽沾,就可以設(shè)置 emitterShape 為 kCAEmitterLayerLine 本慕, emitterMode 為 kCAEmitterLayerOutline。
CAEmitterCell的屬性
其實(shí)CAEmitterCell真是的名字叫粒子侧漓,下面詳細(xì)的介紹了CAEmitterCell的屬性锅尘,只要求大家屬性一下,以后用到了可以再來(lái)查閱布蔗。
@property float birthRate; //每秒生成多少個(gè)粒子
@property float lifetime; //粒子存活的時(shí)間,以秒為單位
@property float lifetimeRange; // 可以為這個(gè)粒子存活的時(shí)間再指定一個(gè)范圍藤违。
上面兩個(gè)屬性如果只用了lifetime那么粒子的存活時(shí)間就是固定的,比如lifetime=10,那么粒子10s秒后就消失了纵揍。
如果使用了lifetimeRange顿乒,比如lifetimeRange=5,那么粒子的存活時(shí)間就是在5s~15s這個(gè)范圍內(nèi)消失泽谨。
@property CGFloat velocity;//粒子平均初始速度璧榄。正數(shù)表示豎直向上特漩,負(fù)數(shù)豎直向下。
@property CGFloat velocityRange; //可以再指定一個(gè)范圍犹菱。
上面兩個(gè)屬性同lifetime和lifetimeRange
@property CGFloat xAcceleration;
@property CGFloat yAcceleration;
@property CGFloat zAcceleration; //三者構(gòu)成了一個(gè)空間矢量拾稳。決定了每個(gè)方向上粒子的加速度吮炕。
@property CGFloat emissionRange; //以錐形分布開(kāi)的發(fā)射角度腊脱。角度用弧度制。粒子均勻分布在這個(gè)錐形范圍內(nèi)龙亲。
@property CGFloat spin;//粒子的平均旋轉(zhuǎn)速度
@property CGFloat spinRange; //可指定一個(gè)范圍陕凹。弧度制鳄炉。
@property(strong) id contents; //cell的內(nèi)容杜耙。通常是一個(gè)指針CGImageRef。
@property CGColorRef color; //可以把圖片「染」成你想要的顏色拂盯。
@property(copy) NSString *name; //The name of the cell佑女,用于構(gòu)建key paths。這也是后面手動(dòng)控制動(dòng)畫開(kāi)始和結(jié)束的關(guān)鍵谈竿。
好团驱,上面簡(jiǎn)單介紹了一下CAEmitterLayer和CAEmitterCell的一些基本屬性,下面來(lái)利用粒子動(dòng)畫實(shí)現(xiàn)一個(gè)類似今日頭條點(diǎn)贊效果空凸。
#import <UIKit/UIKit.h>
@interface GXUpvoteButton : UIButton
@end
#import "GXUpvoteButton.h"
@interface GXUpvoteButton()
/**
展示的layer
*/
@property (strong, nonatomic) CAEmitterLayer *streamerLayer;
/**
圖片數(shù)組
*/
@property (nonatomic, strong) NSMutableArray *imagesArr;
/**
cell的數(shù)組
*/
@property (nonatomic, strong) NSMutableArray *CAEmitterCellArr;
/**
展示多少個(gè)贊的label
*/
@property (nonatomic, strong) UILabel *zanLabel;
@end
@implementation GXUpvoteButton
{
NSTimer *_timer; //定時(shí)器
NSInteger countNum;//贊的個(gè)數(shù)
}
- (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 {
//初始化 贊的個(gè)數(shù)
countNum = 1;
//展示多少個(gè)贊的label
self.zanLabel = [[UILabel alloc]init];
[self addSubview:self.zanLabel];
self.zanLabel.frame = CGRectMake(-50 ,- 100, 200, 40);
self.zanLabel.hidden = YES;
//添加點(diǎn)擊事件
//點(diǎn)一下
[self addGestureRecognizer:[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(pressOnece:)]];
//長(zhǎng)按
[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];
//設(shè)置暫時(shí)的layer
_streamerLayer = [CAEmitterLayer layer];
_streamerLayer.emitterSize = CGSizeMake(30, 30);
_streamerLayer.masksToBounds = NO;
_streamerLayer.renderMode = kCAEmitterLayerAdditive;
[self.layer addSublayer:_streamerLayer];
}
/**
點(diǎn)了一下
@param ges 手勢(shì)
*/
- (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];
//清空數(shù)組
[self.imagesArr removeAllObjects];
[self.CAEmitterCellArr removeAllObjects];
}
}
/**
長(zhǎng)按
@param ges 手勢(shì)
*/
- (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];
}
}
/**
* 開(kāi)始動(dòng)畫
*/
- (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"];
}
/**
* 開(kāi)始噴射
*/
- (void)startAnimate {
for (int i = 1; i < 10; i++)
{
//78張圖片 隨機(jī)選9張
int x = arc4random() % 77 + 1;
NSString * imageStr = [NSString stringWithFormat:@"emoji_%d",x];
[self.imagesArr addObject:imageStr];
}
//設(shè)置展示的cell
for (NSString * imageStr in self.imagesArr) {
CAEmitterCell * cell = [self emitterCell:[UIImage imageNamed:imageStr] Name:imageStr];
[self.CAEmitterCellArr addObject:cell];
}
_streamerLayer.emitterCells = self.CAEmitterCellArr;
// 開(kāi)啟計(jì)時(shí)器 設(shè)置點(diǎn)贊次數(shù)的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開(kāi)始時(shí)間
_streamerLayer.beginTime = CACurrentMediaTime();
for (NSString * imgStr in self.imagesArr) {
NSString * keyPathStr = [NSString stringWithFormat:@"emitterCells.%@.birthRate",imgStr];
[_streamerLayer setValue:@7 forKeyPath:keyPathStr];
}
}
/**
* 停止噴射
*/
- (void)explode {
//讓chareLayer每秒噴射的個(gè)數(shù)為0個(gè)
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;
}
/**
更改點(diǎn)贊個(gè)數(shù)label的文字
*/
- (void)changeText
{
countNum ++;
self.zanLabel.attributedText = [self getAttributedString:countNum];
self.zanLabel.textAlignment = NSTextAlignmentCenter;
}
/**
富文本設(shè)置label的圖片內(nèi)容
@param num 當(dāng)前贊的個(gè)數(shù)
@return 要顯示的富文本
*/
- (NSMutableAttributedString *)getAttributedString:(NSInteger)num
{
//先把num 拆成個(gè)十百
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];
//創(chuàng)建百位顯示的圖片
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];
}
//創(chuàng)建十位顯示的圖片
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];
}
//創(chuàng)建個(gè)位顯示的圖片
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) {
//鼓勵(lì)
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;
}
/**
創(chuàng)建發(fā)射的表情cell
@param image 傳入隨機(jī)的圖片
@param name 圖片的名字
@return cell
*/
- (CAEmitterCell *)emitterCell:(UIImage *)image Name:(NSString *)name
{
CAEmitterCell * smoke = [CAEmitterCell emitterCell];
smoke.birthRate = 0;//每秒出現(xiàn)多少個(gè)粒子
smoke.lifetime = 2;// 粒子的存活時(shí)間
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; //設(shè)置這個(gè) 用來(lái)展示噴射動(dòng)畫 和隱藏
smoke.velocity = 450;//速度
smoke.velocityRange = 30;// 平均速度
smoke.emissionLongitude = 3 * M_PI / 2 ;
smoke.emissionRange = M_PI_2;//粒子的發(fā)散范圍
smoke.spin = M_PI * 2; // 粒子的平均旋轉(zhuǎn)速度
smoke.spinRange = M_PI * 2;// 粒子的旋轉(zhuǎn)速度調(diào)整范圍
return smoke;
}
- (void)layoutSubviews
{
[super layoutSubviews];
//設(shè)置發(fā)射點(diǎn)的位置
_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;
}
調(diào)用方法:
- (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稍后提供...