CALayer之CAEmitterLayer粒子发射器

2018-09-29  本文已影响113人  蜗牛非牛

先发一下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)

具体效果如下:


屏幕快照 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)
    }
}

总结:这篇文章参考了众多大佬的博客,在此表示非常感谢。如果在阅读的时候,发现文章中存在问题,请大家不吝赐教。

参考资料:https://www.jianshu.com/p/c54ffd7412e7

上一篇下一篇

猜你喜欢

热点阅读