先发一下CAEmitterLayer做成的demon效果:
snow.giflike.gif
看过GIF图之后大家应该对CAEmitterLayer充满了好奇,这些该如何实现呢,各位莫慌,只要你耐心看下去,实现这些效果都是小 case。
CAEmitterLayer与CAEmitterCell简介
CAEmitterLayer(粒子发射器)继承自CALayer,是CALayer众多子类中的一个,提供了一个基于Core Animation的粒子发射系统,粒子用CAEmitterCell来初始化,一个单独的CAEmitterLayer可同时支持多个CAEmitterCell。总的来说CAEmitterLayer
是用来发射粒子的,是容器,而它所发射的粒子就是CAEmitterCell,CAEmitterCell用来执行具体的动画效果。当然
除了图中的两种效果之外,CAEmitterLayer还可以实现如:红包雨、烟花、爆炸、火焰等效果。
CAEmitterLayer
与CAEmitterCell
的属性详解
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)
具体效果如下:
屏幕快照 2018-09-29 下午3.26.10.png
实战演练:
demon1❄️:
class SnowViewController: UIViewController {
private var imageArray:Array<UIImage> = []
private var timeArray:Array<NSNumber> = []
private var totalTime:TimeInterval = 0
override 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.0
var 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 = NSData(contentsOfFile: path!)
/** 从data中读取数据,转换为CGImageSource */
let imageSource = CGImageSourceCreateWithData(data!, nil)
let imageCount = CGImageSourceGetCount(imageSource!)
for i in 0..<imageCount {
/** 取出图片 */
let cgImage = CGImageSourceCreateImageAtIndex(imageSource!, i, nil)
let image = UIImage(cgImage: cgImage!)
imageArray.append(image)
/** 取出持续时间 */
let properties = CGImageSourceCopyPropertiesAtIndex(imageSource!, i, nil)! as NSDictionary
let gifDict = properties.value(forKey: kCGImagePropertyGIFDictionary as String) as? NSDictionary
let frameDuration = gifDict![kCGImagePropertyGIFDelayTime] as? NSNumber
totalTime += (frameDuration?.doubleValue)!
}
let imageView = UIImageView(frame: self.view.bounds)
/** 设置imageview的属性 */
imageView.animationImages = imageArray
imageView.animationDuration = totalTime
imageView.animationRepeatCount = 100
/** 开始动画 */
imageView.startAnimating()
return imageView
}()
}
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 = 500
explosionLayer = 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)
}
}
总结:这篇文章参考了众多大佬的博客,在此表示非常感谢。如果在阅读的时候,发现文章中存在问题,请大家不吝赐教。
网友评论