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方面多多指教。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容