有个需求,要求模仿微信做表情下雨的动画,一开始想用CAEmitterLayer,实现的代码如下:

    //期望:显示特效五秒后结束特效
UIImage *image = [UIImage imageNamed:@"snow_white"];
CGRect endRect = self.view.frame;
UIView *layerView = [[UIView alloc]initWithFrame:endRect];
//方便看到有view增加到里面
layerView.backgroundColor = [UIColor colorWithRed:1 green:0 blue:0 alpha:0.1];
layerView.userInteractionEnabled = NO;
CAEmitterLayer * fireworksLayer = [CAEmitterLayer layer];
[layerView.layer addSublayer:fireworksLayer];
[self.view addSubview:layerView];
[layerView.layer addSublayer:fireworksLayer];
fireworksLayer.emitterPosition = CGPointMake(endRect.size.width * 0.5, -image.size.height / 2); // 这个position表示的是粒子产生的位置,注意是图片中心位置的初始值,而不是(x,y)
fireworksLayer.emitterSize = CGSizeMake(endRect.size.width - image.size.width / 2 * 2, 0.f); // 粒子产生的随机区域,如果要让生成的粒子图片不过左右屏幕边缘,记住给左右两边留点空间
fireworksLayer.emitterMode = kCAEmitterLayerOutline;
fireworksLayer.emitterShape = kCAEmitterLayerLine;
fireworksLayer.renderMode = kCAEmitterLayerAdditive;
fireworksLayer.birthRate = 1; // 粒子
CAEmitterCell * cell = [CAEmitterCell emitterCell];
cell.birthRate = 1.f;//每一秒产生的粒子个数(实际数量和上面的birthRate相乘)
cell.lifetime = 5.f;//粒子产生到消失为5秒
cell.velocity = -(endRect.size.height + image.size.height / 2 * 2) / 5;
cell.contents = (id)[image CGImage]; fireworksLayer.emitterCells = @[cell];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 5), dispatch_get_main_queue(), ^{ //移除layer
if (layerView.superview) {
[layerView removeFromSuperview];
}
});

这时候我们能看到效果如此:

这个时候其实我们做到了以下几点

  • view显示5秒并删除
  • 雪花从上向下降落,而且速度是匀速的view高度/5秒
  • 雪花每秒产生1颗
  • 雪花在不超出左右边缘之内随机产生

但是这个效果最大的缺陷在于:雪花的产生和结束都十分的突兀,还需要实现的效果应该是:

  • 效果开始时,雪花正好从顶部开始落下
  • view即将移除的时候,雪花不再生成
  • view移除的时候,最后一片雪花刚好落出屏幕

所以我们的优化也从三个方面进行。

动画起始时间点 beginTime

如果直接查看CAEmitterLayer.h文件,并不能发现beginTime这个属性,甚至其父类CALayer也找不到,直到找到协议CAMediaTiming才能找到。

// CALayer.h
@interface CALayer : NSObject <NSSecureCoding, CAMediaTiming>
...
@end // CAMediaTiming.h
@protocol CAMediaTiming
...
/* The begin time of the object, in relation to its parent object, if
* applicable. Defaults to 0. */ @property CFTimeInterval beginTime;
...
@end

所以我们设置beginTime就可以规定我们想让layer开始动画的时间,但是不应该设置为0(本来就是默认为0),而是CACurrentMediaTime(),也就是现在。

CACurrentMediaTime,文档里面告诉我们这是一个以秒为单位的当前的absolute time,属性为CFTimeInterval(也是double), 在这里“绝对时间”不是某度百科里的提到的时空观或者某个历法,而是mach_absolute_time()——即基于系统启动后的时钟嘀嗒数转换单位为秒的结果,与常见的时间戳[[NSDate date]timeIntervalSince1970]更是没半毛钱没有关系。

   NSLog(@"%lf %lf",[[NSDate date]timeIntervalSince1970], CACurrentMediaTime());
//在某个时空输出为:1646246718.232236 197452.774114

回到问题来,我们只要设置好beginTime,就可以让动画从“现在”开始播放,而默认的0表示的是在“万物之始”开始播放,也难怪我们刚才从屏幕上开始看到的动画不是从零开始的。

    fireworksLayer.beginTime = CACurrentMediaTime();

让雪花停止生成

我们需要在粒子开始运动之后,让粒子生成速度更改为0个/秒,首先明确的是,每秒生成粒子实际数量是fireworksLayer.birthRate * cell.birthRate,那么我们挠挠头就可以添加这段代码。

//细化需求为1s-3s是不断生成粒子,3s生成最后一个粒子后不再生成粒子
.....
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 3), dispatch_get_main_queue(), ^{
fireworksLayer.birthRate = 0;//动画在运动过程中要修改参数的话,可以修改layer的参数,试过在这个过程中只改cell参数不会成功
});

调整速度,让最后一颗粒子正好滚出屏幕时移除view

这里为了方便说明,所以本次只让雪花在y轴上做匀速运动,在x轴上相对静止(不左右移动),也是提醒各位,粒子产生的位置不是图片初始的xy坐标值,而是中心点,这既是是我代码中设置fireworksLayer.emitterPosition会附加图片一半的高度(开始生成粒子时粒子下沿要在view上端)的原因,也是为什么计算速度所用到的总路程时,要加上图片高度一半的两倍(粒子上沿要到view下端才算结束)的原因。

    cell.velocity = -(endRect.size.height + image.size.height / 2 * 2) / 2; //正好最后一朵花开始的时候其图片下沿在屏幕顶部,结束的时候其图片上落到屏幕顶部

加上文章内提交优化后的代码:

    //期望:显示特效五秒后结束特效
UIImage *image = [UIImage imageNamed:@"snow_white"];
CGRect endRect = self.view.frame;
UIView *layerView = [[UIView alloc]initWithFrame:endRect];
//方便看到有view增加到里面
layerView.backgroundColor = [UIColor colorWithRed:1 green:0 blue:0 alpha:0.1];
layerView.userInteractionEnabled = NO;
CAEmitterLayer * fireworksLayer = [CAEmitterLayer layer];
[layerView.layer addSublayer:fireworksLayer];
[self.view addSubview:layerView];
[layerView.layer addSublayer:fireworksLayer];
fireworksLayer.emitterPosition = CGPointMake(endRect.size.width * 0.5, -image.size.height / 2); // 这个position表示的是粒子产生的位置,注意是图片中心位置的初始值,而不是(x,y)
fireworksLayer.emitterSize = CGSizeMake(endRect.size.width - image.size.width / 2 * 2, 0.f); // 粒子产生的随机区域,如果要让生成的粒子图片不过左右屏幕边缘,记住给左右两边留点空间
fireworksLayer.emitterMode = kCAEmitterLayerOutline;
fireworksLayer.emitterShape = kCAEmitterLayerLine;
fireworksLayer.renderMode = kCAEmitterLayerAdditive;
fireworksLayer.birthRate = 1;
fireworksLayer.beginTime = CACurrentMediaTime(); // 粒子
CAEmitterCell * cell = [CAEmitterCell emitterCell];
cell.birthRate = 1.f;//每一秒产生的粒子个数(实际数量和上面的birthRate相乘)
cell.lifetime = 5.f;//粒子产生到消失为5秒
cell.velocity = -(endRect.size.height + image.size.height / 2 * 2) / 2; //正好最后一朵花开始的时候其图片下沿在屏幕顶部,结束的时候其图片上落到屏幕顶部
cell.contents = (id)[image CGImage]; fireworksLayer.emitterCells = @[cell];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 3), dispatch_get_main_queue(), ^{
fireworksLayer.birthRate = 0;//动画在运动过程中要修改参数的话,可以修改layer的参数,试过在这个过程中只改cell参数不会成功
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 5), dispatch_get_main_queue(), ^{ //移除layer
if (layerView.superview) {
[layerView removeFromSuperview];
}
});

效果图:

CAEmitterLayer动画的开始和结束的更多相关文章

  1. 原生js判断css动画结束 css 动画结束的回调函数

    原文:原生js判断css动画结束 css 动画结束的回调函数 css3 的时代,css3--动画 一切皆有可能: 传统的js 可以通过回调函数判断动画是否结束:即使是采用CSS技术生成动画效果,Jav ...

  2. 原生js判断css3动画过度(transition)结束 transitionend事件 以及关键帧keyframes动画结束(animation)回调函数 animationEnd 以及 css 过渡 transition无效

      上图的 demo 主要讲的 是 css transition的过渡回调函数transitionend事件: css3 的时代,css3--动画 一切皆有可能: 传统的js 可以通过回调函数判断动画 ...

  3. IOS动画(Core Animation)总结 (参考多方文章)

    一.简介 iOS 动画主要是指Core Animation框架.官方使用文档地址为:Core Animation Guide. Core Animation是IOS和OS X平台上负责图形渲染与动画的 ...

  4. iOS:动画(18-10-15更)

    目录 1.UIView Animation 1-1.UIView Animation(基本使用) 1-2.UIView Animation(转场动画) 2.CATransaction(Layer版的U ...

  5. iOS开发-动画总结

    一.简介 IOS 动画主要是指Core Animation框架.官方使用文档地址为:Core Animation Guide.Core Animation是IOS和OS X平台上负责图形渲染与动画的基 ...

  6. CSS 3学习——animation动画

    以下内容根据官方文档翻译以及自己的理解整理. 1.  介绍 本方案介绍动画(animations).通过动画,开发者可以将CSS属性值的变化指定为一个随时间变化的关键帧(keyframes)的集合.在 ...

  7. 虾扯蛋:Android View动画 Animation不完全解析

    本文结合一些周知的概念和源码片段,对View动画的工作原理进行挖掘和分析.以下不是对源码一丝不苟的分析过程,只是以搞清楚Animation的执行过程.如何被周期性调用为目标粗略分析下相关方法的执行细节 ...

  8. app引导页(背景图片切换加各个页面动画效果)

    前言:不知不觉中又加班到了10点半,整个启动页面做了一天多的时间,一共有三个页面,每个页面都有动画效果,动画效果调试起来麻烦,既要跟ios统一,又要匹配各种不同的手机,然后产品经理还有可能在中途改需求 ...

  9. Android开发学习——动画

    帧动画> 一张张图片不断的切换,形成动画效果* 在drawable目录下定义xml文件,子节点为animation-list,在这里定义要显示的图片和每张图片的显示时长              ...

  10. SVG动画

    动画原理 SVG动画,就是元素的属性值关于时间的变化. 如下图来说,元素的某个属性值的起始值(from)到结束值(to)在一个时间段(duration)根据时间函数(timing-function)计 ...

随机推荐

  1. dotnet C# 通过 Vortice 使用 Direct2D 的 ID2D1CommandList 入门

    本文将告诉大家如何通过 Vortice 使用 D2D 的 CommandList 功能 本文属于 DirectX 系列博客,更多 DirectX 和 D2D 以及 Vortice 库的博客,请参阅我的 ...

  2. dotnet 将控制台 Console.WriteLine 内容输出到文件

    很多伙伴喜欢使用 Console.WriteLine 打日志,也许是打起来顺手.打完了之后,又想着,要是能够输出到本机文件那就更好了.既然很多伙伴都有这个想法,那 dotnet 自然就是有方便的方法让 ...

  3. Apache Pulsar 桌面端图形化管理工具

    Apache Pulsar 桌面端图形化管理工具 Apache Pulsar 是 Apache 软件基金会顶级项目,是下一代云原生分布式消息流平台,集消息.存储.轻量化函数式计算为一体,采用计算与存储 ...

  4. 为什么我反对过度使用TypeScript?

    前言 在2024年, TypeScript肯定算不上什么新鲜的技术. 但是经过长时间的使用, 我认为可以使用, 但是要适度. 类型跟不上业务的变化 我们知道TypeScript的类型定义是业务的体现. ...

  5. SHELL脚本获取域名对应的IP地址

    单个获取 编写角本pingip.sh #!/bin/sh ADDR=qq.com TMPSTR=`ping ${ADDR} -c 1 | sed '1{s/[^(]*(//;s/).*//;q}'` ...

  6. nim 9. 遍历文件夹

    import std/[os, sugar] const fs = collect(for k in walkDir(r"d:\temp"): k.path) echo fs 文件 ...

  7. 面向K-12学生的远程访问学校计算机实验室

    ​ 为了应对新冠肺炎大流行,许多学校被迫采用远程学习和混合时间制度.在家学习的学生必须使用自己的个人设备或学校提供的设备(例如 Chromebook )来完成课堂作业. 尽管许多解决方案可以帮助学生和 ...

  8. C语言:send + more = money,单词相加求解字母数字谜问题

    我用的是穷举法,虽然有点笨,但是在想不到其他更好的方法对我而言就是穷举法. 有程序员大大想到其他方法也可以私信我一起探讨一下~ #include<stdio.h> int main() { ...

  9. MAC Pro 同时安装 Python2 和 Python3

    目录 文章目录 目录 安装 Python2 安装 Python3 不同版本Python路径 配置 Python2 和 Python3 安装 Python2 MAC 系统已经默认带有 Python2.7 ...

  10. FolkMq v1.4.6 发布(可以内嵌的消息中间件)

    功能简表 角色 功能 生产者(客户端) 发布消息.定时消息(或叫延时).顺序消息.可过期消息.事务消息.支持 Qos0.Qos1 消费者(客户端) 订阅.取消订阅.消费-ACK(自动.手动) 服务端 ...