https://github.com/aliuhongliang/Emitter
模仿今日头条粒子效果,CAEmitterLayer
https://github.com/Qu000/EmitterLayer
直播间粒子动画之一
https://github.com/Ccalary/EmitterDemo
粒子动画 基于CAEmitterLayer实现的下雪和烟花效果
https://github.com/SplashZ/YJFavorEmitter
https://github.com/weizhangCoder/Emitterabel
CAEmitterLayer常用属性
https://www.jianshu.com/p/d02355dd18d8
https://github.com/Joethermal/CloudEmitter
仿真动画(UIDynamicAnimator,UIGravityBehavior,UICollisionBehavior)
https://github.com/JnKindle/UIDynamicAnimator
PAG现已开源
Github地址:
https://github.com/Tencent/libpag
https://pag.art
关键帧动画
https://github.com/shmxybfq/TFDemos/tree/525c377706c84ff53e2afe906d8a528f1471a523
iOS UIKit动力学(五)推动(UIPushBehavior)
https://www.jianshu.com/p/caa5461e0917
仿今日头条点赞喷射表情动画
https://github.com/LifeForLove/GXUpvoteButton
//
// GXUpvoteButton.m
//
// Created by apple on 2017/12/19.
// Copyright © 2017年 getElementByYou. All rights reserved.
//
#import "GXUpvoteButton.h"
@interface GXUpvoteButton()<CAAnimationDelegate>
@property (nonatomic,strong) NSMutableArray *imgArr;
@property (nonatomic,strong) CAEmitterLayer * longPressLayer;
@property (nonatomic,strong) UILongPressGestureRecognizer *longPressGes;
//Core Animation
@property (nonatomic, strong) UIImageView *caStarImageView;
@property (nonatomic, strong) CAEmitterLayer *caStarEmitter;
@property (nonatomic, assign) BOOL shouldShootOffCoreAnimationStar;
@property (nonatomic, strong) UIButton *curBtn;
@end
@implementation GXUpvoteButton {
NSTimer *_timer; //定时器
}
- (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 {
//添加点击事件
//点一下
[self addTarget:self action:@selector(pressOnece:) forControlEvents:UIControlEventTouchUpInside];
//长按
self.longPressGes = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(longPress:)];
[self addGestureRecognizer:self.longPressGes];
[self setImage:[UIImage imageNamed:@"feed_like"] forState:UIControlStateNormal];
[self setImage:[UIImage imageNamed:@"feed_like_press"] forState:UIControlStateSelected];
}
/**
点了一下
*/
- (void)pressOnece:(UIButton *)sender {
if ([self.delegate respondsToSelector:@selector(upvoteBtnTouchUpInsideAction:)]) {
[self.delegate upvoteBtnTouchUpInsideAction:sender];
}
//如果快速点赞
if ([self.imageView.layer animationForKey:@"transform.scale"] && sender.selected) {
[self tapAnimation];
} else {
sender.selected = !sender.selected;
if (sender.selected) {
[self tapAnimation];
} else {
}
}
}
/**
点按动画
*/
- (void)tapAnimation {
[self handimgAnimation];
CAEmitterLayer * layer = [self createStreamLayer];
[self upvoteShowWithStreamLayer:layer];
[self upvoteHiddenWithStreamLayer:layer];
[self feedBackGeneratorAction];
}
/**
振动
*/
- (void)feedBackGeneratorAction {
if (@available(iOS 10.0, *)) {
UIImpactFeedbackGenerator * impactLight = [[UIImpactFeedbackGenerator alloc]initWithStyle:UIImpactFeedbackStyleHeavy];
[impactLight impactOccurred];
} else {
// Fallback on earlier versions
}
}
/**
点赞图片动画
*/
- (void)handimgAnimation {
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;
}else
{
animation.values = @[@0.8, @1.0];
animation.duration = 0.4;
}
animation.calculationMode = kCAAnimationCubic;
animation.delegate = self;
[self.imageView.layer addAnimation:animation forKey:@"transform.scale"];
}
/**
长按
@param ges 手势
*/
- (void)longPress:(UIGestureRecognizer *)ges {
UIButton * sender = (UIButton *)ges.view;
sender.selected = YES;
if (ges.state == UIGestureRecognizerStateBegan) {
_curBtn = sender;
self.shouldShootOffCoreAnimationStar = YES;
_timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(shootOffCoreAnimation) userInfo:nil repeats:YES];
} else if (ges.state == UIGestureRecognizerStateEnded || ges.state == UIGestureRecognizerStateCancelled) {
self.shouldShootOffCoreAnimationStar = NO;
[_timer invalidate];
_timer = nil;
}
}
- (void)shootOffCoreAnimation{
// 在这里编写你希望执行的代码
[self shootOffCoreAnimationStarFromView:_curBtn];
[self longPressTimerAction];
}
/**
定时器事件
*/
- (void)longPressTimerAction {
[self feedBackGeneratorAction];
}
- (void)upvoteShowWithStreamLayer:(CAEmitterLayer *)streamerLayer {
[self createEmitterCellArr:self.imgArr streamerLayer:streamerLayer];
for (NSString * imageStr in self.imgArr) {
[streamerLayer setValue:[NSNumber numberWithInteger:5] forKeyPath:[NSString stringWithFormat:@"emitterCells.%@.birthRate",imageStr]];
}
}
- (void)upvoteHiddenWithStreamLayer:(CAEmitterLayer *)streamerLayer {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
for (NSString * imageStr in self.imgArr) {
[streamerLayer setValue:[NSNumber numberWithInteger:0] forKeyPath:[NSString stringWithFormat:@"emitterCells.%@.birthRate",imageStr]];
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.6 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[streamerLayer removeFromSuperlayer];
});
});
}
- (void)removeLongPressLayer {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
for (NSString * imageStr in self.imgArr) {
[self.longPressLayer setValue:[NSNumber numberWithInteger:0] forKeyPath:[NSString stringWithFormat:@"emitterCells.%@.birthRate",imageStr]];
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.6 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.longPressLayer removeFromSuperlayer];
if (self.longPressLayer) {
self.longPressLayer = nil;
self.longPressGes.enabled = YES;
}
});
});
}
- (CAEmitterLayer *)createStreamLayer {
//设置暂时的layer
CAEmitterLayer *streamerLayer = [CAEmitterLayer layer];
streamerLayer.position = CGPointMake(self.frame.size.width/2.0, self.frame.size.height/2.0);
//发射器的尺寸
streamerLayer.emitterSize = self.layer.bounds.size;
streamerLayer.masksToBounds = NO;
streamerLayer.renderMode = kCAEmitterLayerBackToFront;
return streamerLayer;
}
- (NSMutableArray *)imgArr {
if (_imgArr == nil) {
_imgArr = [NSMutableArray array];
for (int i = 1; i < 8; i++) {
//78张图片 随机选9张
int x = arc4random() % 77 + 1;
NSString * imageStr = [NSString stringWithFormat:@"emoji_%d",x];
[_imgArr addObject:imageStr];
}
}
return _imgArr;
}
/**
创建粒子单元数组
*/
- (NSMutableArray *)createEmitterCellArr:(NSMutableArray *)imgArr streamerLayer:(CAEmitterLayer *)streamerLayer {
//设置展示的cell
NSMutableArray * emitterCellArr = [NSMutableArray array];
for (NSString * imageStr in imgArr) {
CAEmitterCell * cell = [self emitterCell:[UIImage imageNamed:imageStr] Name:imageStr];
[emitterCellArr addObject:cell];
}
streamerLayer.emitterCells = emitterCellArr;
[self.layer addSublayer:streamerLayer];
return emitterCellArr;
}
/**
* 开始动画
*/
- (void)animation {
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];
animation.values = @[@1.5 ,@0.8, @1.0,@1.2,@1.0];
animation.duration = 0.5;
animation.calculationMode = kCAAnimationCubic;
[self.layer addAnimation:animation forKey:@"transform.scale"];
}
/**
创建发射的表情cell
@param image 传入随机的图片
@param name 图片的名字
@return cell
*/
- (CAEmitterCell *)emitterCell:(UIImage *)image Name:(NSString *)name {
CAEmitterCell * smoke = [CAEmitterCell emitterCell];
smoke.birthRate = 0;//每秒创建的发射对象个数
smoke.lifetime = 5;// 粒子的存活时间
smoke.lifetimeRange = 5;
smoke.scale = 0.5;//设置粒子的大小
smoke.alphaRange = 1;
smoke.alphaSpeed = -1.5;
smoke.yAcceleration = 4000;//可以有下落的效果
// smoke.zAcceleration = 4000;
CGImageRef image2 = image.CGImage;
smoke.contents= (__bridge id _Nullable)(image2);
smoke.name = name; //设置这个 用来展示喷射动画 和隐藏
smoke.velocity = 1500;//速度
smoke.velocityRange = 1500;// 平均速度
smoke.emissionRange = M_PI_2;//粒子的发散范围
smoke.emissionLongitude = 3 * M_PI / 2 ;
// smoke.spin = M_PI * 2; // 粒子的平均旋转速度
// smoke.spinRange = M_PI * 2;// 粒子的旋转速度调整范围
return smoke;
}
// Core Animation Example
- (void)shootOffCoreAnimationStarFromView:(UIView *)view {
UIWindow *window = [UIApplication sharedApplication].keyWindow;
CGRect viewFrameInWindow = [view convertRect:window.bounds toView:nil];
int x = arc4random() % 77 + 1;
NSString * imageStr = [NSString stringWithFormat:@"emoji_%d",x];
self.caStarImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:imageStr]];
self.caStarImageView.alpha = 1.0f;
CGRect imageFrame = CGRectMake(viewFrameInWindow.origin.x, viewFrameInWindow.origin.y, 50, 50);
//Your image frame.origin from where the animation need to get start
CGPoint viewOrigin = viewFrameInWindow.origin;
self.caStarImageView.frame = imageFrame;
self.caStarImageView.layer.position = viewOrigin;
[window addSubview:self.caStarImageView];
// particles emitters
self.caStarEmitter = [CAEmitterLayer new];
self.caStarEmitter.emitterPosition = CGPointMake((self.caStarImageView.frame.size.width / 2) - 5, self.caStarImageView.frame.size.height);
self.caStarEmitter.emitterShape = kCAEmitterLayerLine;
self.caStarEmitter.emitterZPosition = 10; // 3;
self.caStarEmitter.emitterSize = CGSizeMake(0.5, 0.5);
self.caStarEmitter.lifetime = 3;
self.caStarEmitter.birthRate = 0.5;
[self.caStarImageView.layer addSublayer:self.caStarEmitter];
// Set up fade out effect
CABasicAnimation *fadeOutAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
[fadeOutAnimation setToValue:[NSNumber numberWithFloat:1.0]];
fadeOutAnimation.fillMode = kCAFillModeForwards;
fadeOutAnimation.removedOnCompletion = NO;
// Set up path movement
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
pathAnimation.calculationMode = kCAAnimationCubicPaced;
pathAnimation.fillMode = kCAFillModeForwards;
pathAnimation.removedOnCompletion = NO;
pathAnimation.rotationMode = kCAAnimationRotateAuto;
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:viewOrigin];
// [path addLineToPoint:CGPointMake(0, (viewOrigin.y-100)<0 ? 30 : viewOrigin.y-100)];
for (int i = 0; i < 5; i++) {
CGFloat randomX = arc4random_uniform([[UIScreen mainScreen] bounds].size.width);
CGFloat randomY = arc4random_uniform([[UIScreen mainScreen] bounds].size.height);
[path addLineToPoint:CGPointMake(randomX, randomY)];
}
// [path addLineToPoint:CGPointMake([[UIScreen mainScreen] bounds].size.width, 0)];
for (int i = 0; i < 4; i++) {
CGFloat randomX = arc4random_uniform([[UIScreen mainScreen] bounds].size.width);
CGFloat randomY = arc4random_uniform([[UIScreen mainScreen] bounds].size.height);
[path addLineToPoint:CGPointMake(randomX, randomY)];
}
for (int i = 0; i < 3; i++) {
CGFloat randomX = arc4random_uniform([[UIScreen mainScreen] bounds].size.width);
CGFloat randomY = arc4random_uniform([[UIScreen mainScreen] bounds].size.height);
[path addLineToPoint:CGPointMake(randomX, randomY)];
}
CGFloat randomX = arc4random_uniform([[UIScreen mainScreen] bounds].size.width); // Generate a random X coordinate
[path addLineToPoint:CGPointMake(randomX, [[UIScreen mainScreen] bounds].size.height + 100)];
pathAnimation.path = path.CGPath;
CAAnimationGroup *group = [CAAnimationGroup animation];
group.fillMode = kCAFillModeForwards;
group.removedOnCompletion = NO;
[group setAnimations:[NSArray arrayWithObjects:fadeOutAnimation, pathAnimation, nil]];
group.duration = 3.0f;
group.delegate = self;
[group setValue:self.caStarImageView forKey:@"imageViewBeingAnimated"];
[self.caStarImageView.layer addAnimation:group forKey:@"savingAnimation"];
}
- (CGFloat)randFloatBetween:(float)low and:(float)high {
float diff = high - low;
return (((float) rand() / RAND_MAX) * diff) + low;
}
#pragma mark - CAAnimationGroup Delegate
- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)finished {
if (finished) {
}
}
@end