CALayer及其各種子類

CoreAnimationXmind.png

這是 Core Animation 的系列文章,介紹了 Core Animation 的用法,以及如何進(jìn)行性能優(yōu)化。

  1. CoreAnimation基本介紹
  2. CGAffineTransform和CATransform3D
  3. CALayer及其各種子類
  4. CAAnimation:屬性動(dòng)畫CABasicAnimation、CAKeyframeAnimation以及過渡動(dòng)畫、動(dòng)畫組
  5. 圖層時(shí)間CAMediaTiming
  6. 計(jì)時(shí)器CADisplayLink
  7. 影響動(dòng)畫性能的因素及如何使用 Instruments 檢測
  8. 圖像IO之圖片加載、解碼,緩存
  9. 圖層性能之離屏渲染、柵格化、回收池

我們已經(jīng)介紹了CALayer類、CGAffineTransform、CATransform3D,但 Core Animation 圖層不止用于設(shè)置圖片、背景色。這一篇文章介紹一些圖層類,進(jìn)一步擴(kuò)展 Core Animation 的能力。

1. CAShapeLayer

在第一篇文章CoreAnimation基本介紹中,介紹了使用CGPath創(chuàng)建任意形狀的陰影,無需使用圖片。如果可以創(chuàng)建任意形狀圖層就更好了。

CAShapeLayer在其坐標(biāo)空間中繪制三次貝塞爾曲線圖層,繼承自CALayer。

CAShapeLayer在 layer 的 contents 和第一個(gè) sublayer 之間合成,CAShapeLayer通過矢量圖形而非位圖繪制。使用CGPath指定顏色、線寬和形狀,CAShapeLayer自動(dòng)渲染圖層。你也可以使用 Core Graphics 直接向CALayercontents繪制路徑,但使用CAShapeLayer有以下這些優(yōu)點(diǎn):

  • 快速。CAShapeLayer使用硬件加速,比使用 Core Graphics 繪制速度快。

  • 節(jié)省內(nèi)存。CAShapeLayer無需像CALayer那樣創(chuàng)建 backing image。因此,不會(huì)隨著 layer 變大,占用更大內(nèi)存。

  • 超出 layer 邊框部分不會(huì)被裁剪。CAShapeLayer可以在bounds外繪制,不會(huì)像使用 Core Graphics 在CALayer繪制的圖形一樣被裁剪掉。

  • 旋轉(zhuǎn)、縮放等變換操作后不會(huì)失真。由于CAShapeLayer是矢量圖(Vector graphics),可以通過數(shù)學(xué)公式計(jì)算獲得。放大時(shí),不會(huì)像位圖(bitmap)那樣放大單個(gè)像素,也就不會(huì)出現(xiàn)線條或形狀鋸齒化的問題。

1.1 創(chuàng)建 CGPath

CAShapeLayer可用于繪制任何可用CGPath表示的形狀。圖形不一定閉合,路徑不一定連續(xù),可以在一個(gè)CAShapeLayer中添加多個(gè) shape。

設(shè)置一些屬性可以改變CAShapeLayer樣式,如fillColor、strokeColor、lineWidthlineCap(線末端樣式)、lineJoin(線之間接頭樣式)等,但一個(gè)CAShapeLayer只能有一個(gè)fillColor、lineDashPattern、lineJoin等。如果需使用不同樣式、顏色,需創(chuàng)建多個(gè) shape layer。

下面代碼顯示了使用CAShapeLayer繪制線筆畫,CAShapeLayerpath屬性是CGPathRef類型。這里使用UIBezierPath創(chuàng)建 path,省去了手動(dòng)釋放CGPath的步驟。如下所示:

        // Create path
        let path = UIBezierPath()
        path.move(to: CGPoint(x: 175, y: 100))
        path.addArc(withCenter: CGPoint(x: 150, y: 100), radius: 25, startAngle: 0, endAngle: .pi * 2, clockwise: true)
        path.move(to: CGPoint(x: 150, y: 125))
        path.addLine(to: CGPoint(x: 150, y: 175))
        path.addLine(to: CGPoint(x: 125, y: 225))
        path.move(to: CGPoint(x: 150, y: 175))
        path.addLine(to: CGPoint(x: 175, y: 225))
        path.move(to: CGPoint(x: 100, y: 150))
        path.addLine(to: CGPoint(x: 200, y: 150))
        
        // Create shape layer
        let shapelLayer = CAShapeLayer()
        shapelLayer.strokeColor = UIColor.red.cgColor
        shapelLayer.fillColor = UIColor.clear.cgColor
        shapelLayer.lineWidth = 5
        shapelLayer.lineJoin = .bevel
        shapelLayer.lineCap = .round
        shapelLayer.path = path.cgPath
        
        // Add it to our view
        view.layer.addSublayer(shapelLayer)

效果如下:

CAShapeLayer.png

1.2 圓角

使用CAShapeLayer可以創(chuàng)建圓角矩形。與cornerRadius相比,CAShapeLayer允許指定單個(gè)角半徑。下面代碼創(chuàng)建三個(gè)圓角、一個(gè)直角的矩形:

        let rect = CGRect(x: 0, y: 0, width: 100, height: 100)
        let radii = CGSize(width: 20, height: 20)
        let path = UIBezierPath.init(roundedRect: rect, byRoundingCorners: [.topRight, .bottomRight, .bottomLeft], cornerRadii: radii)

CAShapeLayerpath屬性設(shè)置上述貝塞爾曲線,可以獲得圓角、直角組合的矩形。如果想要將 layer 的contents設(shè)置為同樣圖形,可以將CAShapeLayer賦值給mask屬性。如下所示:

        // Create path
        let rect = CGRect(x: 0, y: 0, width: 100, height: 100)
        let radii = CGSize(width: 20, height: 20)
        let path = UIBezierPath.init(roundedRect: rect, byRoundingCorners: [.topRight, .bottomRight, .bottomLeft], cornerRadii: radii)
        
        let layer = CALayer()
        layer.backgroundColor = UIColor.gray.cgColor
        layer.position = view.center
        layer.bounds = CGRect(x: 0, y: 0, width: 100, height: 100)
        
        // Create mask layer
        let maskLayer = CAShapeLayer()
        maskLayer.path = path.cgPath
        layer.mask = maskLayer
        
        view.layer.addSublayer(layer)

效果如下:

CAMask.png

CALayermask屬性是CALayer類,使用方法與 sublayer 類似,相對于擁有它的圖層布局自身位置。與普通 sublayer 不同,mask不是在父圖層內(nèi)繪制,其決定了父圖層的可見區(qū)域。

mask的顏色不重要,重要的是它的輪廓。與mask重合部分會(huì)被保留下來,mask以外部分會(huì)被隱藏。

CAMaskedImage.jpg

如果masklayer小于父圖層,則只有與mask相交的父圖層部分可見,其他部分都會(huì)被隱藏。

2. CATransformLayer

在 3D 場景中,創(chuàng)建對象的層級結(jié)構(gòu)并將變換應(yīng)用于根視圖,整個(gè)層級結(jié)構(gòu)會(huì)隨之變換。

向容器中添加四個(gè)圖層,不添加任何變換。如下所示:

CAContainerLayer.png

旋轉(zhuǎn)每個(gè) layer Y軸后,得到如下四個(gè)圖層:

CADistinctRotation.png

CALayer不能管理 3D 層級結(jié)構(gòu)中的深度,其只能將Z軸場景展平到單個(gè)層級。為了解決這個(gè)問題,需使用CATransformLayer。

與其他 layer 不同,CATransformLayer不會(huì)將 sublayer 展平到 Z=0 的平面中,因此,它不支持CALayer的眾多功能:

  • 只渲染CATransformLayer的 sublayer。transform layer 的backgroundColor、contents、邊緣樣式、描邊樣式等都不會(huì)生效。
  • 2D 圖像處理的屬性會(huì)被忽略。包含filters、backgroundFilters、compositingFilter、mask、masksToBounds和陰影樣式等。
  • opacity屬性會(huì)被單獨(dú)應(yīng)用到每個(gè) sublayer,transform layer 不會(huì)形成合成組。
  • Transform layer 沒有 2D 坐標(biāo)空間概念,不能將自身點(diǎn)映射到二維空間。因此,不要對 transform layer 應(yīng)用hitTest:方法。

下面代碼創(chuàng)建了四個(gè) layer,其具有相同的x、y坐標(biāo),不同z坐標(biāo)。

    private func testTransformLayerA() {
        // Create the container as a CATransformLayer
        let container = CATransformLayer()
        
        // 如果使用CALayer,不能得到三維圖層。
//        let container = CALayer()
        container.frame = view.frame
        view.layer.addSublayer(container)
        
        // Planes data
                let planesPosition = view.layer.position
        let planeSize = CGSize(width: 100, height: 100)
        
        // Create 4 planes
        let purplePlane = addPlane(to: container, size: planeSize, position: planesPosition, color: UIColor.purple)
        let redPlane = addPlane(to: container, size: planeSize, position: planesPosition, color: UIColor.red)
        let orangePlane = addPlane(to: container, size: planeSize, position: planesPosition, color: UIColor.orange)
        let yellowPlane = addPlane(to: container, size: planeSize, position: planesPosition, color: UIColor.yellow)
        
        // Apply transform to the container
        var t = CATransform3DIdentity
        t.m34 = 1.0 / -500
        t = CATransform3DRotate(t, .pi/3, 0, 1, 0)
        container.transform = t
        
        // Apply transform to the planes
        t = CATransform3DIdentity
        t = CATransform3DTranslate(t, 0, 0, 0)
        purplePlane.transform = t
        
        // Apply transform to the planes
        t = CATransform3DIdentity
        t = CATransform3DTranslate(t, 0, 0, -40)
        redPlane.transform = t
        
        // Apply transform to the planes
        t = CATransform3DIdentity
        t = CATransform3DTranslate(t, 0, 0, -80)
        orangePlane.transform = t
        
        // Apply transform to the planes
        t = CATransform3DIdentity
        t = CATransform3DTranslate(t, 0, 0, -120)
        yellowPlane.transform = t
    }
    
    private func addPlane(to container: CALayer, size: CGSize, position: CGPoint, color: UIColor) -> CALayer {
        let plane = CALayer()
        plane.backgroundColor = color.cgColor
        plane.opacity = 0.6
        plane.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
        plane.position = position
        plane.borderColor = UIColor.init(white: 1.0, alpha: 0.5).cgColor
        plane.borderWidth = 3
        plane.cornerRadius = 10
        container.addSublayer(plane)
        
        return plane
    }

運(yùn)行結(jié)果如下:

CATransformLayer.png

如果使用CALayer替代CATransformLayer,效果如下:

CATransformCALayer.png

3. CAGradientLayer

CAGradientLayer繪制背景色漸變的圖層。

Gradient layer 用于創(chuàng)建包含任意數(shù)量顏色的顏色漸變。默認(rèn)情況下,顏色均勻分布在整個(gè)圖層上,但可以使用locations屬性指定顏色位置。

CAGradientLayer有以下屬性:

  • locations:元素為浮點(diǎn)類型的數(shù)組,值范圍為0至1,且只能遞增。如果為nil,則均勻排布。默認(rèn)為nil。
  • colors:元素為CGColorRef類型的數(shù)組,默認(rèn)為nil
  • startPoint:在圖層坐標(biāo)空間繪制時(shí),漸變的起點(diǎn)。使用單位坐標(biāo)系,并在繪制時(shí)映射到 layer 點(diǎn)坐標(biāo)。默認(rèn)值為(0.5, 0.5)。
  • endPoint:在圖層坐標(biāo)空間繪制時(shí),漸變的終點(diǎn)。使用單位坐標(biāo)系,并在繪制時(shí)映射到 layer 點(diǎn)坐標(biāo)。默認(rèn)值為(0.5, 1.0)。

下面代碼展示了如何創(chuàng)建包含三種顏色、指定漸變位置的圖層:

                gradient.colors = [UIColor.red.cgColor, UIColor.yellow.cgColor, UIColor.green.cgColor]
        gradient.locations = [0.0, 0.25, 0.5]
        
        gradient.startPoint = CGPoint(x: 0, y: 0)
        gradient.endPoint = CGPoint(x: 1, y: 1)

效果如下:

CAGradientLayer.png

4. CAReplicatorLayer

CAReplicatorLayer用于創(chuàng)建 layer 的指定數(shù)量副本,副本間有不同的幾何坐標(biāo)、顯示屬性(delay、transform)和顏色等。常用屬性如下:

  • instanceCount:要?jiǎng)?chuàng)建的副本數(shù),包括原始 layer。默認(rèn)值時(shí)1,即不創(chuàng)建副本。
  • instanceDelay:指定副本顯示延時(shí)。默認(rèn)值為0.0秒,即同步顯示。
  • instanceTransform:向前一個(gè)副本添加 transform,得到當(dāng)前副本。默認(rèn)為CATransform3DIdentity
  • preservesDepth:是否將子圖層展平到平面中。默認(rèn)為false。如果為true,則CAReplicatorLayer表現(xiàn)與CATransformLayer相似,同時(shí)受CATransformLayer同樣限制。
  • instanceColor:指定原始圖層的顏色。默認(rèn)為不透明白色。
  • instanceRedOffset:指定顏色紅色通道偏移量。向 k-1 實(shí)例添加偏移,得到 k 實(shí)例顏色。默認(rèn)為0.0。

instanceGreenOffset、instanceBlueOffsetinstanceAlphaOffsetinstanceRedOffset類似,只是通道不同。

下面的代碼在屏幕中央創(chuàng)建一個(gè)白色的 layer,使用CAReplicatorLayer創(chuàng)建由十個(gè) layer 構(gòu)成圓形的圖案。

        var replicatorLayer = CAReplicatorLayer()
        replicatorLayer.bounds = CGRect(x: 0, y: 0, width: view.bounds.size.width, height: view.bounds.size.height)
        view.layer.addSublayer(replicatorLayer)
        
        // Configure the replicator
        replicatorLayer.instanceCount = 10
        
        // Apply a transform for each instance
        var transform = CATransform3DIdentity
        transform = CATransform3DTranslate(transform, 0, 200, 0)
        transform = CATransform3DRotate(transform, .pi / 5.0, 0, 0, 1)
        transform = CATransform3DTranslate(transform, 0, -200, 0)
        replicatorLayer.instanceTransform = transform
        
        // Apply a color shift for each instance
        replicatorLayer.instanceBlueOffset = -0.1
        replicatorLayer.instanceGreenOffset = -0.1
        
        // Create a sublayer and place it inside the replicator
        let layer = CALayer()
        layer.bounds = CGRect(x: 0, y: 0, width: 100, height: 100)
        layer.position = view.layer.position
        layer.backgroundColor = UIColor.white.cgColor
        
        replicatorLayer.addSublayer(layer)

效果如下:

CAReplicatorLayer.png

CAReplicatorLayer可用于游戲中導(dǎo)彈發(fā)射后軌跡、粒子發(fā)射效果。此外,還可以用于鏡像圖片。

設(shè)置負(fù)值的縮放因子可以獲得鏡像。這里將其封裝為單獨(dú)視圖,后續(xù)使用時(shí)只需繼承自ReflectionView即可。

class ReflectionView: UIView {
    
    override class var layerClass: AnyClass {
        return CAReplicatorLayer.self
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        setup()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setup()
    }
    
    private func setup() {
        let layer = self.layer as! CAReplicatorLayer
        layer.instanceCount = 2
        
        // Move reflection instance below original and flip vertically
        var transform = CATransform3DIdentity
        let verticalOffset = self.bounds.size.height + 2
        transform = CATransform3DTranslate(transform, 0, verticalOffset, 0)
        transform = CATransform3DScale(transform, 1, -1, 0)
        layer.instanceTransform = transform
        
        // Reduce alpha of reflection layer
        layer.instanceAlphaOffset = -0.6
    }
}

效果如下:

CAReflection.png

開源項(xiàng)目ReflectionView實(shí)現(xiàn)了自適應(yīng)漸變淡出效果,淡出效果使用CAGradientLayer和 mask 實(shí)現(xiàn)。

5. CAScrollLayer

對于沒有進(jìn)行變換的 layer,bounds的大小與frame的大小一致。frame是由boundsposition派生而來。因此,改變一個(gè)會(huì)影響另一個(gè)。

如果想展示大圖層的一部分應(yīng)該如何做?例如,有一個(gè)很大的圖片,或者一個(gè)長列表、文本,希望用戶可以隨意滑動(dòng)。在 iOS 中,可以使用UITableViewUIScrollView,Core Animation 中對應(yīng)的 layer 是什么呢?

想要展示大圖一部分時(shí),可以使用contentsRect屬性,但當(dāng)你的圖層有 sublayer 時(shí),每次滑動(dòng)時(shí)都需要手動(dòng)計(jì)算、更新所有 sublayer 位置,這樣非常麻煩。

這時(shí)可以使用CAScrollLayer,CAScrollLayerscroll(to:)方法自動(dòng)調(diào)整bounds的原點(diǎn),使圖層內(nèi)容看起來是在滑動(dòng)。由于 Core Animation 不能識別用戶手勢,因此其不能將手勢轉(zhuǎn)換為滑動(dòng)事件,另外也不會(huì)渲染滑動(dòng)狀態(tài)條和滑動(dòng)彈性效果。

下面使用CAScrollLayer創(chuàng)建一個(gè)類似UIScrollView的替代控件。創(chuàng)建一個(gè)自定義UIView,使用CAScrollLayer作為 backing layer,使用UIPanGestureRecognizer處理手勢。代碼如下:

class ScrollView: UIView {
    
    override class var layerClass: AnyClass {
        return CAScrollLayer.self
    }
    
    private func setup() {
        // Enable clipping
        layer.masksToBounds = true
        backgroundColor = UIColor.lightGray
        
        // Attach pan gesture recognizer
        let recognizer = UIPanGestureRecognizer(target: self, action: #selector(self.pan(_:)))
        addGestureRecognizer(recognizer)
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        setup()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        
        setup()
    }
    
    @objc func pan(_ recognizer: UIPanGestureRecognizer) {
        // Get the offset by subtracting the pan gesture
        // Transform from the current bounds origin
        var offset = self.bounds.origin
        offset.x -= recognizer.translation(in: self).x
        offset.y -= recognizer.translation(in: self).y
        
        // Scroll the layer
        layer.scroll(offset)
        
        // Reset the pan gesture translation
        recognizer.setTranslation(CGPoint.zero, in: self)
    }
}

如下所示:

CAScrollLayer.png

CAScrollLayer類的以下方法實(shí)現(xiàn)了滾動(dòng)功能:

  • scroll(to: CGPoint):將 layer 的原點(diǎn)設(shè)置為指定點(diǎn)。
  • scroll(to: CGRect):滾動(dòng)內(nèi)容,確保指定矩形區(qū)域可見。

我們使用CAScrollLayer實(shí)現(xiàn)的 ScrollView 類沒有進(jìn)行任何邊界檢測,內(nèi)容可能會(huì)劃出可見區(qū)域并可繼續(xù)滾動(dòng)。CAScrollLayer沒有UIScrollViewcontentSize概念,因此沒有總可滑動(dòng)區(qū)域概念。也就是劃動(dòng)CAScrollLayer時(shí),它只是調(diào)整bounds原點(diǎn)到指定位置。

既然可以通過調(diào)整CALayerbounds獲得同樣效果,什么情況下需要使用CAScrollLayer?事實(shí)上很少使用CAScrollLayer,UIScrollView沒有使用CAScrollLayer,而是直接操控 layer 的bounds進(jìn)行滾動(dòng)。

6. CATiledLayer

有時(shí)需要繪制的圖片特別大,而移動(dòng)設(shè)備內(nèi)存非常有限,因此讀取整個(gè)圖片到內(nèi)存不是一種好的解決方案。

載入大圖會(huì)非常慢,常用的init(named:)contentsOfFile:方法會(huì)堵塞主線程,導(dǎo)致卡頓。圖片最大大小受設(shè)備內(nèi)存限制。屏幕上顯示的圖片最終都會(huì)被轉(zhuǎn)換為 OpenGL texture,而 OpenGL texture 有一個(gè)最大的大?。ㄍǔ?048*2048或4096*4096,因設(shè)備而異)。

如果要顯示的圖片大于單個(gè) texture,即使圖片已經(jīng)存在于內(nèi)存中了,Core Animation 也必須使用 CPU 而非 GPU 處理圖片,這時(shí)會(huì)明顯感受到內(nèi)存問題。

CATiledLayer通過把大圖分割為小圖解決上述性能問題。當(dāng)需要渲染更多區(qū)域時(shí),在一個(gè)或多個(gè)后臺線程調(diào)用draw(in:)方法,為繪制操作提供數(shù)據(jù)。Drawing context 提供了 clip bounds 和 transform matrix,用于確定請求圖塊的分辨率和 bounds。

使用setNeedsDisplay(_:)方法使圖層指定區(qū)域無效,但更新是異步的。且下一次的更新很可能不包含更新的內(nèi)容,但后續(xù)的更新會(huì)包含。

6.1 顯示多個(gè)小圖

下面展示一張大圖(2048*2048)。為了獲得CATiledLayer的性能提升,需將大圖分割為多張小圖。雖然可以使用代碼分割圖片,但如果在運(yùn)行時(shí)加載圖片并分割,將會(huì)失去CATiledLayer提供的性能提升。這里直接使用分割好的小圖,小圖大小為256*256,共64張。

CATiledLayer添加到UIScrollView使用,并實(shí)現(xiàn)draw(in:)方法。當(dāng)CATiledLayer需要加載新圖片時(shí),會(huì)調(diào)用draw(in:)方法。

    private func testTiledLayer() {
        view.addSubview(scrollView)
        
        // Add the tiled layer
        let tileLayer = CATiledLayer()
        tileLayer.frame = CGRect(x: 0, y: 0, width: 2048, height: 2048)
        tileLayer.delegate = self
        scrollView.layer.addSublayer(tileLayer)
        
        // Configure the scroll view.
        scrollView.contentSize = tileLayer.frame.size
        
        // Draw layer
        tileLayer.setNeedsDisplay()
    }
    
extension LayersViewController: CALayerDelegate {
    func draw(_ layer: CALayer, in ctx: CGContext) {
        guard let layer = layer as? CATiledLayer else  {
            return
        }
        
        // Determine tile coordinate
        let bounds = ctx.boundingBoxOfClipPath
        let x: Int = Int(floor(bounds.origin.x / layer.tileSize.width))
        let y: Int = Int(floor(bounds.origin.y / layer.tileSize.height))
        
        // Load tile image
        let imgName = "Snowman_0\(x)_0\(y)"
        let imgPath = Bundle.main.path(forResource: imgName, ofType: "jpg")
        guard let imgLocation = imgPath else { return }
        let tileImage = UIImage(contentsOfFile: imgLocation)
        
        // Draw tile
        UIGraphicsPushContext(ctx)
        tileImage?.draw(in: bounds)
        UIGraphicsPopContext()
    }
}

如下所示:

CATiledLayer.png

當(dāng)滑動(dòng)圖片,會(huì)發(fā)現(xiàn)CATiledLayer載入小圖的時(shí)候會(huì)淡入到屏幕中,這是CATiledLayer的默認(rèn)行為,可以使用fadeDuration屬性改變淡入時(shí)長或直接禁用掉。CATiledLayer不同于大部分UIKit和 Core Animation API,它支持多線程繪制,draw(in:)方法可能在多線程并行調(diào)用,需確保該方法內(nèi)的繪制代碼線程安全。

不要嘗試直接修改CATiledLayercontents屬性,因?yàn)檫@樣會(huì)禁用它的異步機(jī)制,使其和普通的CALayer沒有區(qū)別。

7. CAEmitterLayer

CAEmitterLayer是一個(gè)高性能的粒子引擎,用來創(chuàng)建實(shí)時(shí)粒子動(dòng)畫。例如,煙霧、火、雨等。

CAEmitterLayerCAEmitterCell實(shí)例的容器,CAEmitterCell定義了粒子效果。創(chuàng)建一個(gè)或多個(gè)CAEmitterCell對象作為不同類型粒子的模版,CAEmitterLayer基于模版產(chǎn)生粒子流。

CAEmitterCell繼承自NSObject,和CALayer非常類似。CAEmitterCellcontents屬性可以定義為一個(gè)CGImage,還有很多屬性用于配制粒子的外觀和行為。這里不會(huì)詳細(xì)介紹每一個(gè)屬性,你可以在CAEmitterCell文檔中查看詳細(xì)介紹。

下面創(chuàng)建擁有不同速度、透明度的粒子,以視圖中心為emitterPosition向四周發(fā)射的爆炸效果。

        // Create particle emitter layer
        var replicatorLayer = CAReplicatorLayer()
        emitter.position = view.layer.position
        emitter.bounds = view.bounds
        view.layer.addSublayer(emitter)

        // Configure emitter
        emitter.renderMode = .additive
        emitter.emitterPosition = view.center

        // Create a particle template
        let cell = CAEmitterCell()
        cell.contents = UIImage(named: "Spark")?.cgImage
        cell.birthRate = 150
        cell.lifetime = 5
        cell.color = UIColor(red: 1.0, green: 0.5, blue: 0.1, alpha: 1.0).cgColor
        cell.alphaSpeed = -0.4
        cell.velocity = 50
        cell.velocityRange = 50
        cell.emissionRange = .pi * 2.0

        // Add particle template to emitter
        emitter.emitterCells = [cell]

如下所示:

CAEmitterLayer.png

CAEmitterCell屬性可分為三類:

  • 屬性初始值,如color屬性指定一個(gè)可以混合contents圖片的顏色。在上述示例中,color被設(shè)置為橘色。
  • 屬性的變化范圍。上述示例中,emissionRange被設(shè)置為360度,表示粒子可以向任意方向發(fā)射,粒子之間角度具有一定差值??梢酝ㄟ^設(shè)置一個(gè)小角度創(chuàng)建錐形效果。
  • 屬性隨時(shí)間的變化。上述示例中,alphaSpeed值為-0.4,表示粒子的alpha每秒減少0.4,創(chuàng)建一種粒子遠(yuǎn)離過程中逐漸消失的效果。

CAEmitterLayer屬性控制整個(gè)粒子系統(tǒng)的位置和形狀。CAEmitterLayer的有些屬性與CAEmitterCell屬性相同,設(shè)置CAEmitterLayer的屬性后,會(huì)與CAEmitterCell屬性相乘。使用CAEmitterLayer屬性可以控制整個(gè)粒子系統(tǒng)效果。還有以下兩個(gè)重要屬性:

  • preservesDepth:定義是否將粒子展平到平面中,默認(rèn)為false。如果為true,則該圖層將其粒子渲染為位于該圖層上層的三維坐標(biāo)空間。啟用后,layer 的filters、backgroundFilters和陰影相關(guān)屬性效果是未定義的。
  • renderMode:控制粒子圖層在視覺上如何融合,默認(rèn)值為unordered。示例中使用additive,即重疊部分亮度增加。

CAEmitterLayerscaleseedspin等屬性乘數(shù),只影響新創(chuàng)建的粒子,已經(jīng)發(fā)射出粒子不受影響。例如,emitter 的scale值為1,發(fā)射一些粒子后修改scale為2。此時(shí),已經(jīng)發(fā)射出去的粒子大小不受影響,仍保持原來大小,新創(chuàng)建的粒子大小變?yōu)樵瓉矶丁?/p>

總結(jié)

這一部分介紹了多種圖層,以及使用這些圖層可以實(shí)現(xiàn)的效果。像CATiledLayerCAEmitterLayer等類都可以單獨(dú)寫成一篇文章,這里只作簡單介紹。另外,CATextLayer、CAMetaLayer、AVPlayerLayer也是CALayer的子類,這篇文章并未介紹,可以自行查閱文檔。

CALayer并沒有針對所有情況都進(jìn)行性能優(yōu)化。如果想要達(dá)到最佳性能,需根據(jù)需求選擇合適子類。下一篇文章CAAnimation:屬性動(dòng)畫CABasicAnimation、CAKeyframeAnimation以及過渡動(dòng)畫、動(dòng)畫組將介紹顯式動(dòng)畫。

Demo名稱:CoreAnimation
源碼地址:https://github.com/pro648/BasicDemos-iOS/tree/master/CoreAnimation

上一篇:CGAffineTransform和CATransform3D

下一篇:CAAnimation:屬性動(dòng)畫CABasicAnimation、CAKeyframeAnimation以及過渡動(dòng)畫、動(dòng)畫組

參考資料:

  1. Introduction to 3D drawing in Core Animation (Part 1)

歡迎更多指正:https://github.com/pro648/tips

本文地址:https://github.com/pro648/tips/blob/master/sources/CALayer及其各種子類.md

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容