美文网首页
CALayer之CAEmitterLayer粒子发射器

CALayer之CAEmitterLayer粒子发射器

作者: 蜗牛非牛 | 来源:发表于2018-09-29 15:24 被阅读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

    相关文章

      网友评论

          本文标题:CALayer之CAEmitterLayer粒子发射器

          本文链接:https://www.haomeiwen.com/subject/snxboftx.html