有个需求,要求模仿微信做表情下雨的动画,一开始想用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. WPF 框架开发 调试和开发 XAML 构建过程的 PresentationBuildTasks 方法

    阅读本文,你可以了解如何编写开发和调试 XAML 构建为 Baml 和 g.cs 文件的过程和工具.本文也适合想要了解 WPF 的 XAML 构建过程的开发者阅读,本文提供了可以断点调试 WPF 的 ...

  2. dotnet 读 WPF 源代码笔记 插入触摸设备的初始化获取设备信息

    在 WPF 触摸应用中,插入触摸设备,即可在应用里面使用上插入的触摸设备.在 WPF 使用触摸设备的触摸时,需要获取到触摸设备的信息,才能实现触摸 获取触摸设备插入 在 WPF 中,通过 Window ...

  3. SuperSonic简介

    SuperSonic融合ChatBI和HeadlessBI打造新一代的数据分析平台.通过SuperSonic的问答对话界面,用户能够使用自然语言查询数据,系统会选择合适的可视化图表呈现结果. Supe ...

  4. 开源电子邮件营销平台 listmonk 使用教程

    做产品肯定要做电子邮件营销,特别是面向海外的产品,电子邮件营销已成为企业与客户沟通.建立品牌忠诚度和推动销售的重要工具,可以直接接触到目标受众,提供个性化内容,并以相对较低的成本获得可观的投资回报.你 ...

  5. go 操作elasticsearch

    olivere/elastic 包 github.com/olivere/elastic doc: https://pkg.go.dev/github.com/olivere/elastic?utm_ ...

  6. .NET6 Minimal API 集成Autofac

    前提 集成Autofac前需要先添加两个依赖包 可以通过 NuGet 进行安装,使用以下命令: dotnet add package Autofac dotnet add package Autofa ...

  7. .NET 采用开源软件OpenOffice 实现文档转码服务(word ppt excel)转PDF

    前言 前几年做了个项目,里面有个需求,需要在浏览器中在线浏览word excel ppt  pdf等文档. 最近又开始研究并记录下来 当时方案: 因为浏览器中阅读文档暂时只能通过pdf方式读取,所以就 ...

  8. Linux搭建ES集群环境

    搭建ES集群环境 准备 三台服务器 其中一台为主机节点 ES安装自行上传到各个节点home路径下并解压重命名 集群名称:cluster-big-data同一个集群多个节点,集群名称必须相同,节点名称不 ...

  9. 文件系统(五):exFAT 文件系统原理详解

    前言 exFAT是微软2006年推出的一种文件系统,距今已快二十年,相比于FAT16和FAT32,exFAT还是算年轻.exFAT一直是微软的一个专用文件系统,直到2019年微软发布它的规范,目前微软 ...

  10. nginx37条优化

    nginx优化: 1. cpu:核心配置 方法1: worker_processes auto; 自动调用[推荐] 方法2: worker_processes 4; 手工配置 检查CPU核心:yum ...