Core Animation 第六章 專用圖層(下)

往期回顧:
序章
第一章 - 圖層樹
第二章 - 寄宿圖
第三章 - 圖層幾何
第四章 - 視覺效果
第五章 - 變換
第六章 專用圖層(上)
項目中使用的代碼

CAGradientLayer

相信大家或多或少的都遇到過漸變過渡背景的需求,這種時候你完全可以選擇讓設計為你切一個圖片背景,但是為什么不嘗試自己來繪制一個呢?CAGradientLayer就是這樣一個用來將顏色平穩過渡的圖層。
下面我們先來看一個例子再來解釋如何使用CAGradientLayer

class CAGradientLayerViewController: UIViewController {
    @IBOutlet weak var containerView: UIView!
    override func viewDidLoad() {
        let gradientLayer = CAGradientLayer()
        gradientLayer.frame = self.containerView.bounds
        self.containerView.layer.addSublayer(gradientLayer)
        gradientLayer.colors = [UIColor.red.cgColor, UIColor.blue.cgColor]
        gradientLayer.startPoint = CGPoint(x: 0, y: 0)
        gradientLayer.endPoint = CGPoint(x: 1, y: 1)
    }
}
紅藍兩色對角線漸變

可以看到CAGradientLayer的時候方法非常簡單,你只需要規定好漸變的起始點startPoint,終點endPoint已經你希望混合的顏色colorsCoreAnimation就會平穩的過渡這些顏色。

非均勻漸變

大家可能已經注意到了,上面兩種顏色的過渡是完全均勻的,但是我們也會遇到不完全變換的情況,這種時候我們就需要用到CAGradientLayer的另一個屬性: locations。這個屬性用來標記每種顏色變化的范圍,locations數組中元素的數量需要跟colors中的屬相相同。

非均勻漸變

    override func viewDidLoad() {
        let gradientLayer = CAGradientLayer()
        gradientLayer.frame = self.containerView.bounds
        gradientLayer.colors = [UIColor.red.cgColor, UIColor.yellow.cgColor, UIColor.green.cgColor]
        gradientLayer.locations = [0.0, 0.25, 0.5]
        gradientLayer.startPoint = CGPoint(x: 0, y: 0)
        gradientLayer.endPoint = CGPoint(x: 1, y: 1)
        self.containerView.layer.addSublayer(gradientLayer)
    }

CAReplicatorLayer

重復圖層,跟他的名字一樣,這個圖層主要用來高效的生成許多相思的圖層。CAReplicatorLayer在使用的時候需要指定一個instanceCount(指定這個圖層需要重復多少次)以及一個instanceTransform(指定每次重復的時候相對于上一次的3D變化效果),下面簡單寫一個例子。

override func viewDidLoad() {
        super.viewDidLoad()
        let replicatorLayer = CAReplicatorLayer()
        replicatorLayer.frame = containerView.bounds
        self.containerView.layer.addSublayer(replicatorLayer)
        replicatorLayer.instanceCount = 10
        var transform = CATransform3DIdentity
        transform = CATransform3DTranslate(transform, 0, 200, 0)
        transform = CATransform3DRotate(transform, CGFloat(M_PI / 5.0), 0, 0, 1)
        transform = CATransform3DTranslate(transform, 0, -200, 0)
        replicatorLayer.instanceTransform = transform
        replicatorLayer.instanceBlueOffset = -0.1
        replicatorLayer.instanceGreenOffset = -0.1
        let layer = CALayer()
        layer.frame = replicatorLayer.bounds
        layer.backgroundColor = UIColor.white.cgColor
        replicatorLayer.addSublayer(layer)
    }
重復圖層示例

CAScrollLayer

我們都知道,當我們想要找的的內容超出了視圖的bounds的時候,我們可以使用UIScrollView以及他的子類UITableView或者UICollectionView來實現滾動展示。當然,CoreAnimation也有對應的圖層,那就是CAScrollLayer。不過因為他是一個圖層,所以CAScrollLayer并不能處理用戶的觸摸事件并把它轉化為滾動事件,自然也不會自己為你處理類似UIScrollViewDecelerating效果以及反彈效果。如果你想使用CAScrollLayer代替UISCrollView,那么你需要通過UIView來處理用戶的手勢,然后將它體現在CAScrollLayer上面,而且你還需要處理代理等相關信息,所以說就滾動視圖而言CAScrollLayer并不能算得上是一個明確的選擇。

CATiledLayer

我們常用的加載圖片的方式是 UIImage-imageNamed:或者imageWithContentsOfFile:方法,但是這些方法會阻塞主線程,所以在你加載大圖的時候你的app可能會出現卡頓,而且如果圖片過大還會出現其他的問題,下面引用作者的原話:

所有顯示在屏幕上的圖片最終都會被轉化為OpenGL紋理,同時OpenGL有一個最大的紋理尺寸(通常是2048x2048,或4096x4096,這個取決于設備型號)。如果你想在單個紋理中顯示一個比這大的圖,即便圖片已經存在于內存中了,你仍然會遇到很大的性能問題,因為Core Animation強制用CPU處理圖片而不是更快的GPU。

而CATiledLayer則可以將大圖轉換為若干小圖,然后將他們單獨按需加載(瓦片圖)。
下面我們先寫一個簡單的例子將一張4096x4096的皮卡丘分割為若干小圖。

let argv = ProcessInfo.processInfo.arguments
guard argv.count >= 2 else {
    assertionFailure("TileCutter arguments: inputfile")
    exit(-1)
}

let inputFile = String.init(cString: argv[1], encoding: String.Encoding.utf8)! as NSString

let titleSize: CGFloat = 256.0

let outputPath = inputFile.deletingPathExtension

let image: NSImage = NSImage(contentsOfFile: inputFile as String)!
var size = image.size
let representations = image.representations
if !representations.isEmpty {
    let representation = representations[0]
    size.width = CGFloat(representation.pixelsWide)
    size.height = CGFloat(representation.pixelsHigh)
}
var rect = NSRect(x: 0.0, y: 0.0, width: size.width, height: size.height)
let imageRef = image.cgImage(forProposedRect: &rect, context: nil, hints: nil)

let rows = Int(ceil(size.height / titleSize))
let cols = Int(ceil(size.width / titleSize))

for y in 0..<rows {
    for x in 0..<cols {
        let titleRect = CGRect(x: CGFloat(x)*titleSize, y: CGFloat(y)*titleSize, width: titleSize, height: titleSize)
        let titleImage = imageRef!.cropping(to: titleRect)
        
        let imageRep = NSBitmapImageRep(cgImage: titleImage!)
        let data = imageRep.representation(using: NSJPEGFileType, properties: [:])
        let path = outputPath.appendingFormat("_%02i_%02i.jpg", x, y)
        let fileURL = URL(fileURLWithPath: path)
        do {
            try data?.write(to: fileURL)
        } catch _ {}
    }
}

運行這個swift的腳本的時候記得在命令行追加自己的圖片路徑,或者在Xcode中追加scheme參數

修改Edit Scheme

接下來回到我們的CATiledLayer。CATiledLayer可以很好的ScrollView結合在一起。

class CATiledLayerViewController: UIViewController, CALayerDelegate {
    @IBOutlet weak var scrollView: UIScrollView!

    override func viewDidLoad() {
        super.viewDidLoad()
        let tiledLayer = CATiledLayer()
        tiledLayer.frame = CGRect(x: 0, y: 0, width: 4096, height: 4096)
        tiledLayer.delegate = self
        scrollView.layer.addSublayer(tiledLayer)
        scrollView.contentSize = tiledLayer.frame.size
        tiledLayer.setNeedsDisplay()
        tiledLayer.contentsScale = UIScreen.main.scale
    }
    
    func draw(_ layer: CALayer, in ctx: CGContext) {
        let tileLayer = layer as! CATiledLayer
        let bounds = ctx.boundingBoxOfClipPath
        let scale = UIScreen.main.scale
        let x = Int(floor(bounds.origin.x / tileLayer.tileSize.width * scale))
        let y = Int(floor(bounds.origin.y / tileLayer.tileSize.height * scale))
        let imageName = String.init(format: "pica_big_%02i_%02i", x, y)
        let imagePath = Bundle.main.path(forResource: imageName, ofType: "jpg")
        let tileImage = UIImage(contentsOfFile: imagePath!)
        UIGraphicsPushContext(ctx)
        tileImage?.draw(in: bounds)
        UIGraphicsPopContext()
    }
}

CAEmitterLayer

CAEmitterLayer是在iOS5.0中引入的一個高性能的粒子引擎。CAEmitterLayer可以看做是許多CAEmitterCell的容器。下面我們來寫一個簡單的例子,由于屬性EmitterLayerEmitterCell的屬性很多,所以這里不再一一贅述,大家可以再文檔中查閱。

override func viewDidLoad() {
        super.viewDidLoad()
        let emitter = CAEmitterLayer()
        emitter.frame = containerView.bounds
        containerView.layer.addSublayer(emitter)
        
        emitter.renderMode = kCAEmitterLayerAdditive
        emitter.emitterPosition = CGPoint(x: emitter.frame.size.width / 2.0, y: emitter.frame.size.height / 2.0)
        
        let cell = CAEmitterCell()
        cell.contents = UIImage(named: "Sparkle")?.cgImage
        cell.birthRate = 150
        cell.lifetime = 5.0
        cell.alphaSpeed = -0.3
        cell.velocity = 50
        cell.velocityRange = 50
        cell.emissionRange = CGFloat(M_PI) * 2.0
        emitter.emitterCells = [cell]
    }

小結

書中在后面還提到了CAEAGLLayer和AVPlayerLayer,前者為GLKView中的專用圖層,由于本人對于OpenGL了解甚少,所以不再贅述,如果大家感興趣的話我在單獨整理一次,后者雖然是CALyaer的子類但是并不屬于CoreAnimation框架所以這里也先跳過了。
另注:本次將原書中的代碼轉換為了swift,所以也歡迎大家對swift方面多多指教。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容