美文网首页
ios 利用粒子动画实现今天头像点赞效果

ios 利用粒子动画实现今天头像点赞效果

作者: 逆光少年 | 来源:发表于2017-12-29 17:41 被阅读1195次
    image.png

    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 <UIKit/UIKit.h>
    
    @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稍后提供...

    相关文章

      网友评论

          本文标题:ios 利用粒子动画实现今天头像点赞效果

          本文链接:https://www.haomeiwen.com/subject/muqwgxtx.html