CALayer之CAEmitterLayer粒子发射器

先发一下CAEmitterLayer做成的demon效果:
snow.gif
like.gif 看过GIF图之后大家应该对CAEmitterLayer充满了好奇,这些该如何实现呢,各位莫慌,只要你耐心看下去,实现这些效果都是小 case。
CAEmitterLayer与CAEmitterCell简介 CAEmitterLayer(粒子发射器)继承自CALayer,是CALayer众多子类中的一个,提供了一个基于Core Animation的粒子发射系统,粒子用CAEmitterCell来初始化,一个单独的CAEmitterLayer可同时支持多个CAEmitterCell。总的来说CAEmitterLayer是用来发射粒子的,是容器,而它所发射的粒子就是CAEmitterCell,CAEmitterCell用来执行具体的动画效果。当然除了图中的两种效果之外,CAEmitterLayer还可以实现如:红包雨、烟花、爆炸、火焰等效果。
CAEmitterLayerCAEmitterCell的属性详解 CAEmitterLayer属性

/** 用于存储cell */ open var emitterCells: [CAEmitterCell]? /** 粒子的产生率,默认1;每个粒子cell的产生率乘以这个粒子产生系数,得出每一秒产生这个粒子的个数。 即:每秒粒子产生个数 = layer.birthRate * cell.birthRate */ open var birthRate: Float /** 粒子的生命周期,以秒为单位。默认0 */ open var lifetime: Float /** 决定发射源的中心点 */ open var emitterPosition: CGPoint /** 发射器在z平面的位置 */ open var emitterZPosition: CGFloat /** 决定发射源的大小 */ open var emitterSize: CGSize /** 发射器的深度 */ open var emitterDepth: CGFloat /** 表示粒子从什么形状发射出来,它并不是表示粒子自己的形状。是一个枚举类型,提供如下类型可供选择: * kCAEmitterLayerPoint 点形状,发射源的形状就是一个点 * kCAEmitterLayerLine 线形状,发射源的形状是一条线 * kCAEmitterLayerRectangle 矩形状,发射源的形状是一个矩形 * kCAEmitterLayerCuboid 立体矩形形状(3D),发射源是一个立体矩形,这里要生效的话需要设置z方向的数据,如果不设置就同矩形状 * kCAEmitterLayerCircle 圆形形状,发射源是一个圆形 * kCAEmitterLayerSphere 立体圆形(3D),三维的圆形,同样需要设置z方向数据,不设置则通二维一样 */ open var emitterShape: String /** 发射模式,这个字段规定了在特定形状上发射的具体形式是什么。它的作用其实就是进一步决定发射的区域是在发射形状的哪一部份,提供如下类型可供选择: * kCAEmitterLayerPoints 点模式,发射器是以点的形式发射粒。发射点就是形状的某个特殊的点,比如shap是一个点的话,那么这个点就是中心点,如果是圆形,那么就是圆心 * kCAEmitterLayerOutline 轮廓模式,从形状的边界上发射粒子 * kCAEmitterLayerSurface 表面模式,从形状的表面上发射粒子 * kCAEmitterLayerVolume 是相对于3D形状的“球体内”或“立方体内”发射 */ open var emitterMode: String /** 渲染模式,,提供如下类型可供选择: * kCAEmitterLayerUnordered 粒子无序出现 * kCAEmitterLayerOldestFirst 声明久的粒子会被渲染在最上层 * kCAEmitterLayerOldestLast 年轻的粒子会被渲染在最上层 * kCAEmitterLayerBackToFront 粒子的渲染按照Z轴的前后顺序进行 * kCAEmitterLayerAdditive 粒子混合 */ open var renderMode: String /** 是否开启三维效果 */ open var preservesDepth: Bool /** 粒子速度系数, 默认1.0;粒子速度 = layer.velocity * cell.velocity */ open var velocity: Float /** 粒子的缩放比例系数, 默认1.0,计算方式同上 */ open var scale: Float /** 自旋转速度系数, 默认1.0,计算方式同上 */ open var spin: Float /** 随机数发生器 */ open var seed: UInt32

CAEmitterCell属性
/** 粒子名字, 默认为nil */ open var name: String? /** 粒子是否渲染 */ open var isEnabled: Bool /** 粒子的产生率,默认0 */ open var birthRate: Float /** 粒子的生命周期,以秒为单位。默认0 */ open var lifetime: Float /** 粒子的生命周期的范围,以秒为单位。默认0 */ open var lifetimeRange: Float /** 指定纬度,纬度角代表了在x-z轴平面坐标系中与x轴之间的夹角,默认0 */ open var emissionLatitude: CGFloat /** 指定经度,经度角代表了在x-y轴平面坐标系中与x轴之间的夹角,默认0 */ open var emissionLongitude: CGFloat /** 发射角度范围,默认0,以锥形分布开的发射角度。角度用弧度制。粒子均匀分布在这个锥形范围内 */ open var emissionRange: CGFloat /** 速度和速度范围,两者默认0 */ open var velocity: CGFloat open var velocityRange: CGFloat /** x,y,z方向上的加速度分量,三者默认都是0 */ open var xAcceleration: CGFloat open var yAcceleration: CGFloat open var zAcceleration: CGFloat /** 缩放比例, 默认是1 */ open var scale: CGFloat /** 缩放范围与缩放速度, 默认是0 */ open var scaleRange: CGFloat open var scaleSpeed: CGFloat /** 粒子的平均旋转速度和范围,默认是0 */ open var spin: CGFloat open var spinRange: CGFloat /** 粒子的颜色,默认白色 */ open var color: CGColor? /** 粒子颜色red、green、blue、alpha能改变的范围,默认0 */ open var redRange: Float open var greenRange: Float open var blueRange: Float open var alphaRange: Float /**粒子颜色red,green,blue,alpha在生命周期内的改变速度,默认都是0 */ open var redSpeed: Float open var greenSpeed: Float open var blueSpeed: Float open var alphaSpeed: Float /** 粒子的内容,为CGImageRef的对象 */ open var contents: Any? /** 渲染范围 */ open var contentsRect: CGRect /** 内容缩放 */ open var contentsScale: CGFloat /** 粒子里面的粒子,可以设置粒子之间的依托关系 */ open var emitterCells: [CAEmitterCell]? /** 以下三种是一种叫做拉伸过滤的算法,它作用于原图的像素上并根据需要生成新的像素显示在屏幕上。具有以下三种模式: * kCAFilterLinear 这个过滤器采用双线性滤波算法,它在大多数情况下都表现良好。双线性滤波算法通过对多个像素取样最终生成新的值,得到一个平滑的表现不错的拉伸。但是当放大倍数比较大的时候图片就模糊不清了。 * kCAFilterTrilinear 三线性滤波算法,存储了多个大小情况下的图片(也叫多重贴图),并三维取样,同时结合大图和小图的存储进而得到最后的结果。 * kCAFilterNearest 是一种比较武断的方法。从名字不难看出,这个算法(也叫最近过滤)就是取样最近的单像素点而不管其他的颜色。这样做非常快,也不会使图片模糊。但是,最明显的效果就是,会使得压缩图片更糟,图片放大之后也显得块状或是马赛克严重。 */ /** 缩小图片,是默认的过滤器 */ open var minificationFilter: String /** 放大图片 */ open var magnificationFilter: String /** 减小大小的因子 */ open var minificationFilterBias: Float

对于滤波器有兴趣的童鞋可以参考拉伸过滤这篇文章。
在CAEmitterCell中,所有带有range的参数是一种范围内随即发生的事件。几个:
snowCell.emissionLongitude = CGFloat(M_PI_2) snowCell.emissionRange = CGFloat(M_PI_4)

具体效果如下:

CALayer之CAEmitterLayer粒子发射器
文章图片
屏幕快照 2018-09-29 下午3.26.10.png 实战演练: demon1??:
class SnowViewController: UIViewController { private var imageArray:Array = [] private var timeArray:Array = [] private var totalTime:TimeInterval =0override func viewDidLoad() { super.viewDidLoad() self.title = "雪" self.view.backgroundColor = UIColor.white self.view.addSubview(imageView!) imageView?.layer.addSublayer(snowLayer!) } /** snowLayer懒加载 */ lazy var snowLayer:CAEmitterLayer? = { var snowLayer = CAEmitterLayer() /** 发射源的形状 是枚举类型 */ snowLayer.emitterShape = kCAEmitterLayerLine /** 发射模式 枚举类型 */ snowLayer.emitterMode = kCAEmitterLayerSurface /** 发射源的size 决定了发射源的大小 */ snowLayer.emitterSize = self.view.frame.size /** 发射源的位置 */ snowLayer.emitterPosition = CGPoint(x: self.view.frame.size.width*0.5, y: -10) /** 每秒产生的粒子数量的系数 */ snowLayer.birthRate = 6.0var snowCell = CAEmitterCell() /** 粒子的内容 是CGImageRef类型的 */ snowCell.contents = UIImage(named: "snow_white")?.cgImage /** 每秒产生的粒子数量 */ snowCell.birthRate = 10 /** 粒子的生命周期 */ snowCell.lifetime = 200.0 /** 粒子的速度 */ snowCell.velocity = 4.0/** 粒子再y方向的加速的 */ snowCell.yAcceleration = 20.0 snowCell.velocityRange = 10.0 /** 粒子的缩放比例 */ snowCell.scale = 0.15 snowCell.scaleRange = 0.14 /** 向下 */ snowCell.emissionLongitude = CGFloat(M_PI_2); snowCell.emissionRange = CGFloat(M_PI_4); /** 粒子添加到CAEmitterLayer上 */ snowLayer.emitterCells = [snowCell] return snowLayer }()lazy var imageView:UIImageView? = { /** 加载GIF图片,并转化为data类型 */ let path = Bundle.main.path(forResource: "snow.gif", ofType: nil) let data = https://www.it610.com/article/NSData(contentsOfFile: path!) /** 从data中读取数据,转换为CGImageSource */ let imageSource = CGImageSourceCreateWithData(data!, nil) let imageCount = CGImageSourceGetCount(imageSource!) for i in 0..

demon2:
class LikeButton:UIButton{ var explosionLayer:CAEmitterLayer? override func awakeFromNib() { super.awakeFromNib() setupExplosion() }override init(frame: CGRect) { super.init(frame: frame) setupExplosion() }required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }func setupExplosion() { let explosionCell = CAEmitterCell() explosionCell.name = "explosionCell" /** 粒子的内容 是CGImageRef类型的 */ explosionCell.contents = UIImage(named: "spark_blue")?.cgImage explosionCell.alphaSpeed = -1.0 explosionCell.alphaRange = 0.10 /** 粒子的生命周期 */ explosionCell.lifetime = 1.0 explosionCell.lifetimeRange = 0.1 /** 粒子的速度 */ explosionCell.velocity = 40.0 explosionCell.velocityRange = 10.0 /** 粒子的缩放比例 */ explosionCell.scale = 0.08 explosionCell.scaleRange = 0.02 explosionCell.birthRate = 500explosionLayer = CAEmitterLayer() explosionLayer?.name = "" /** 发射源的形状 是枚举类型 */ explosionLayer?.emitterShape = kCAEmitterLayerCircle /** 发射模式 枚举类型 */ explosionLayer?.emitterMode = kCAEmitterLayerOutline explosionLayer?.renderMode = kCAEmitterLayerOldestFirst /** 发射源的size 决定了发射源的大小 */ explosionLayer?.emitterSize = CGSize(width: self.bounds.size.width+40, height: self.bounds.size.height+40) /** 粒子添加到CAEmitterLayer上 */ explosionLayer?.emitterCells = [explosionCell] explosionLayer?.birthRate = 0 self.layer.addSublayer(explosionLayer!) explosionLayer?.position = CGPoint(x: self.bounds.size.width*0.5, y: self.bounds.size.height*0.5) }override func layoutSubviews() {super.layoutSubviews() }override func sendAction(_ action: Selector, to target: Any?, for event: UIEvent?) { let animaton = CAKeyframeAnimation(keyPath: "transform.scale") self.isSelected = !self.isSelected if self.isSelected{ animaton.values = [1.5,2.0, 0.8, 1.0] animaton.duration = 0.5 animaton.calculationMode = kCAAnimationCubic self.layer.add(animaton, forKey: nil) self.perform(#selector(startAnimation), with: nil, afterDelay: 0.25) }else{ animaton.values = [0.8, 1.0] animaton.duration = 0.4 self.layer.add(animaton, forKey: nil) stopAnimation() } }@objc func startAnimation() { explosionLayer?.birthRate = 10 explosionLayer?.beginTime = CACurrentMediaTime() self.perform(#selector(stopAnimation), with: nil, afterDelay: 0.25) }@objc func stopAnimation() { explosionLayer?.setValue(0, forKey: "birthRate") explosionLayer?.removeAllAnimations() } }class LikeViewController: UIViewController {override func viewDidLoad() { super.viewDidLoad() self.title = "点赞" self.view.backgroundColor = UIColor.white let likeBtn = LikeButton(frame: CGRect(x: 100, y: 150, width: 30, height: 30)) likeBtn.setImage(UIImage(named: "dislike"), for: .normal) likeBtn.setImage(UIImage(named: "liek_orange"), for: .selected) likeBtn.addTarget(self, action: #selector(likeBtnAction(btn:)), for: .touchUpInside) self.view.addSubview(likeBtn) } }

总结:这篇文章参考了众多大佬的博客,在此表示非常感谢。如果在阅读的时候,发现文章中存在问题,请大家不吝赐教。
【CALayer之CAEmitterLayer粒子发射器】参考资料:https://www.jianshu.com/p/c54ffd7412e7

    推荐阅读