這篇文章是我在看《iOS Core Animation: Advanced Techniques 》這本書的時候做的一些筆記,這本書很深入的將了Core Animation的原理性的東西般哼,是一本講解Core Animation原理非常深入的書燥爷,豆瓣評分有9.5 。如果把整本書全部讀完谅辣,理解修赞,我相信iOS 中的動畫就是件很輕松的事情了,可惜讀的是英文的桑阶,很多東西還是沒有搞懂柏副,摘抄了一些我自己覺得有用的點(diǎn),翻譯了一下蚣录,可能代碼比較少割择,大部分是原理性的東西,希望能對大家理解CALayer有一定的幫助萎河。
在網(wǎng)上也找到了這本書完整的中文翻譯荔泳,如果感興趣蕉饼,可以去看看。
https://www.gitbook.com/book/zsisme/ios-/details
1.The Layer Tree(圖層樹)
Core Animation這個名字很容易讓人產(chǎn)生誤解玛歌,你可能會覺得他主要的目的是動畫昧港,但是其實(shí)動畫只是這個框架的一方面。
**CALayer: **
CALayer在概念上跟UIView非常像支子,也是一個矩形的對象创肥,可以形成一個層級樹。CALayer中可以包含內(nèi)容(例如圖片值朋,文字叹侄,或者背景顏色等),也可以管理其中的子圖層(sublayer)昨登,有可以用來動畫和變換的屬性和方法圈膏。
每個UIView都內(nèi)部都包含一個layer對象(backing layer),UIView的渲染,布局和動畫篙骡,都是通過這個layer對象來管理的稽坤。但是UIView最顯著的特性之一:用戶交互,不是通過CALayer來管理的糯俗,因?yàn)镃ALayer不支持響應(yīng)鏈尿褪,不能響應(yīng)事件(但是CALayer提供了方法來判斷點(diǎn)擊事件是否在某個layer的bounds之內(nèi))
我是這樣理解的,CALayer就是一個不能進(jìn)行用戶交互的UIView得湘,UIView中大部分顯示都是通過CALayer來完成的杖玲,而UIView自己負(fù)責(zé)用戶交互的這一部分。
1.為什么iOS要設(shè)計(jì)這樣的UIView與CALayer的平行分層淘正?而不是只用一層UIView來處理所有的顯示和用戶交互摆马?
為了分離職責(zé),減少重復(fù)代碼鸿吆。事件處理和用戶交互在iOS和Mac OS上差別很大(最基本的用戶交互差別是iOS是基于觸摸操作囤采,Mac OS基于鼠標(biāo)和鍵盤的操作,這也是為什么iOS中是UIKit和UIView而Mac OS中是AppKit和NSView惩淳,它們雖然功能像似蕉毯,但是實(shí)現(xiàn)方法不同),但是相反的是繪圖思犁,布局和動畫等代虾,在iOS和Mac OS中概念基本相同。通過將其中的邏輯抽離出來到獨(dú)立的Core Animation框架中激蹲,Apple可以在iOS平臺和Mac OS平臺中進(jìn)行代碼共享棉磨。
2.CALayer可以做什么?
常見的:
- 繪制陰影,圓角学辱,著色邊界
- 3D變換和定位
- 非矩形邊界
- 多步乘瓤,非線性動畫
3.使用CALayer
CALayer的使用非常簡單环形,下面這個方法是最基礎(chǔ)的使用,可以看到跟UIView的使用非常像馅扣。
func addLayer() {
let layer = CALayer()
layer.frame = CGRectMake(100, 100, 100, 100)
layer.backgroundColor = UIColor.redColor().CGColor
view.layer.addSublayer(layer)
}
4.什么情況下你會選擇使用CALayer,而不是UIView着降?
- 當(dāng)你想要寫一份可以同時在iOS和Mac上運(yùn)行的代碼
- 你可能需要使用多種CALayer的子類(后面會提到)
- 當(dāng)你的程序?qū)π阅芤蠓浅8叩臅r候差油,可能管理UIView額外的對象都對性能有影響的時候
2.The Backing Image(寄宿圖)
這個我也不知道咋翻譯。任洞。蓄喇。看網(wǎng)上有翻譯成寄宿圖的交掏,就借過來用一下了妆偏。其實(shí)感覺就是layer的一個填充圖片內(nèi)容的容器。
CALayer有一個屬性叫做:contents盅弛,在OC中是id類型钱骂,swift中是AnyObject?類型,這代表它可以是任意類型的對象挪鹏。但是你會發(fā)現(xiàn)如果給contents賦值CGImage類型以外的對象時见秽,得到的都是空白的結(jié)果。這是Mac OS中遺留下來的問題讨盒。之所以設(shè)計(jì)為id類型是為了在Mac OS中可以賦值CGImage或者NSImage,并且都可以得到想要的結(jié)果解取,但是在iOS中,如果賦值UIImage得到的也是一個空白的結(jié)果返顺。
//下面這段代碼可以不通過UIImageView而是通過CALayer禀苦,在屏幕上顯示一張圖片
let layer = CALayer()
layer.frame = CGRectMake(100, 100, 100, 100)
layer.contents = UIImage(named: "layer.jpg")?.CGImage
view.layer.addSublayer(layer)
其實(shí)對于UIView的很多屬性的操作,其實(shí)只是操作UIView內(nèi)部layer對應(yīng)的屬性
- contentsGravity : 相當(dāng)于UIView中的contentMode屬性遂鹊。但是它是String類型的振乏,而不是枚舉(CALayer中對應(yīng)的大部分UIView中的枚舉屬性都是以KCA開頭的String)。contentsGravity也是用來判斷l(xiāng)ayer的內(nèi)容如何對齊秉扑。
- masksToBounds : 對應(yīng)的是UIView中的clipsToBounds屬性昆码。
還有很多其他的屬性,就不一一列舉了,大家可以看一下CALayer和UIView對應(yīng)的屬性邻储,很多名稱差不多的赋咽,可能就是相對應(yīng)的。
自定義繪圖
除了contents可以設(shè)置背景圖片以外吨娜,我們可以直接使用Core Graphics來直接繪制圖形來填充到backing layer中脓匿,-drawRect: 方法可以在UIView的子類中重寫來實(shí)現(xiàn)自定義繪圖。只要UIView發(fā)現(xiàn)drawRect方法實(shí)現(xiàn)了宦赠,就會自動生成backing layer陪毡,所以如果你不需要這個背景圖片的話米母,最好不要留一個空白的drawRect方法,因?yàn)檫@樣會浪費(fèi)內(nèi)存毡琉。
-drawRect: 方法在view第一次顯示在屏幕上時自動調(diào)用铁瞒,在這個方法中用Core Graphics來繪制圖形到背景圖片中,圖形會一直緩存在內(nèi)存中直到view需要刷新(一般都是通過手動調(diào)用 -setNeedsDisplay方法來實(shí)現(xiàn)桅滋,但是view有一些屬性的改變也會引起重繪慧耍,比如bounds)
CALayer有一個可選的delegate屬性,遵循CALayerDelegate協(xié)議(非正式協(xié)議)丐谋,當(dāng)CALayer需要內(nèi)容信息的時候芍碧,會從delegate中取獲取。
當(dāng)layer需要重繪的時候号俐,CALayer會調(diào)用delegate的
- (void)displayLayer:(CALayerCALayer *)layer;
方法來獲取背景圖片泌豆。
如果delegate沒有實(shí)現(xiàn)上面的方法,CALayer會自動創(chuàng)建一個空的背景圖片和繪制的圖形上下文(context)吏饿,作為調(diào)用delegate的
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
方法的參數(shù)踪危。
現(xiàn)在你應(yīng)該理解和知道如何使用CALayerDelegate了,但是除非你是在創(chuàng)建獨(dú)立的layers猪落,否則基本上不用手動去實(shí)現(xiàn)CALayerDelegate協(xié)議陨倡。這是因?yàn)閁IView在創(chuàng)建backing layer的時候會自動設(shè)置自己為layer的delegate并且提供-displayLayer:方法的實(shí)現(xiàn)。在使用UIView的backing layer的時候许布,你不需要去實(shí)現(xiàn)-displayLayer:方法或者-drawLayer:inContext:方法來繪制內(nèi)容到layer的backing image兴革,只需要實(shí)現(xiàn)-drawRect:方法,UIView會自動處理蜜唾,包括在需要重繪的時候會自動調(diào)用 -display方法杂曲。
3.Layout Geometry(圖層幾何)
Layout
- UIView 三個主要的layout屬性: frame, bounds, center
- CALayer 的三個layout屬性: frame , bounds, position
UIView的frame,bounds和center其實(shí)只是內(nèi)部layer相對應(yīng)屬性的get和set方法袁余,當(dāng)你改變一個view的frame的時候擎勘,你其實(shí)改變的是內(nèi)部layer的frame。
錨點(diǎn)(anchorPoint) :
一個layer的錨點(diǎn)代表的是它的哪個點(diǎn)在position(相對于super layer)的位置,所以錨點(diǎn)與position總是重合的颖榜,默認(rèn)的錨點(diǎn)是在layer的中心點(diǎn)上棚饵。錨點(diǎn)是在單位坐標(biāo)中描述的,左上角的位置是{0,0}掩完,右下角的位置是{1,1}噪漾,默認(rèn)位置是{0.5,0.5}。
坐標(biāo)系統(tǒng)
layer跟view一樣且蓬,也是相對于自己的父圖層來放置的欣硼,layer的position是以superlayer的bounds為參照的。如果superlayer移動的話恶阴,所有的sublayers都要跟著移動诈胜,這樣的好處是我們可以通過移動最底部的superlayer可以把所有的sublayer作為一個單元一起移動豹障。但是,有時候我們需要知道的是一個layer相對于另外一個layer的位置焦匈,而不是相對于它的superlayer的位置血公。CALayer提供了一些方法來在layer之間的坐標(biāo)系進(jìn)行轉(zhuǎn)換:
- (CGPoint)convertPoint:(CGPoint)point fromLayer:(CALayer *)layer;
- (CGPoint)convertPoint:(CGPoint)point toLayer:(CALayer *)layer;
- (CGRect)convertRect:(CGRect)rect fromLayer:(CALayer *)layer;
- (CGRect)convertRect:(CGRect)rect toLayer:(CALayer *)layer;
Hit Testing
一般情況下我們會使用帶有backing layer的UIView而不是直接使用CALayer來創(chuàng)建視圖,很重要的一個原因是缓熟,layer處理觸摸事件或者手勢非常復(fù)雜累魔。CALayer不了解響應(yīng)鏈,所有不能直接相應(yīng)觸摸或者手勢荚虚,但是它提供了兩個方法來讓我們自己實(shí)現(xiàn)觸摸操作薛夜,兩個方法的參數(shù)都是一個CGPoint籍茧,代表當(dāng)前觸摸的點(diǎn)
- -containsPoint: 判斷點(diǎn)是否在layer的frame之內(nèi)
- -hitTest: 返回layer变屁,或者包含觸摸點(diǎn)的最深層的sublayer,當(dāng)點(diǎn)不在layer上時舆乔,返回nil
layer masking
layer的mask屬性也是一個CALayer對象,設(shè)置layer的mask屬性與直接添加一個sublayer類似,但是顯示的時候并不是一樣的艾杏。作為mask的layer定義的是parent layer的可視部分,而不是直接繪制在parent layer中絮吵。所以开皿,mask的color等屬性設(shè)置沒有任何影響,只有它的輪廓才是最重要的部分漓帚。mask的作用像是一個切割的工具母债,只有parent layer中mask包含的那一部分會被切出來并且保留。
5.Transforms(變換)
下面幾個單詞我總是混淆尝抖,經(jīng)常會用到毡们。
- transform :變換
- translate : 平移
- transition : 過渡
- transcation : 事務(wù)處理(動畫中)
1.仿射變換
CGAffineTransform(3*3的矩陣) 代表的是 二維平面 的 旋轉(zhuǎn)(rotation),縮放(scale)昧辽,平移(translation)
仿射變換(Affine) 代表的是平面之內(nèi)變化之前平行的線衙熔,變化之后依然平行
創(chuàng)建CGAffineTransform
CGAffineTransformMakeRotation(CGFloat angle)
CGAffineTransformMakeScale(CGFloat sx, CGFloat sy)
CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty)
仿射變換結(jié)合
//通過已經(jīng)存在的變換生成新的變換
CGAffineTransformRotate(CGAffineTransform t, CGFloat angle)
CGAffineTransformScale(CGAffineTransform t, CGFloat sx, CGFloat sy)
CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat ty)
單位矩陣
CGAffineTransformIdentity
兩個已經(jīng)存在的變換矩陣結(jié)合
CGAffineTransformConcat(CGAffineTransform t1, CGAffineTransform t2)
2.3D變換
CATransform3D是一個4*4的矩陣,可以在三維空間內(nèi)對一個對象進(jìn)行選擇搅荞,縮放红氯,評、平移等變換咕痛。
CATransform3D和CGAffineTransform提供的方法很像
CATransform3DMakeRotation(CGFloat angle, CGFloat x, CGFloat y, CGFloat z)
CATransform3DMakeScale(CGFloat sx, CGFloat sy, CGFloat sz)
CATransform3DMakeTranslation(Gloat tx, CGFloat ty, CGFloat tz)
translation和scale相比CGAffineTransform多了一個代表z軸的z參數(shù)痢甘,rotation方法接收x,y茉贡,z和一個angle參數(shù)(一起形成了一個代表旋轉(zhuǎn)軸的矢量)
6.Specialized Layers(特殊的layer)
-
CAShapeLayer
CAShapeLayer繪制的是矢量圖形产阱,而不是位圖。通過CGPath定義一個合適的性狀块仆,指定顏色构蹬,線寬等屬性來渲染出一個layer王暗。
與直接在CALayer的contents中通過Core Graphics來繪制圖形相比,CAShapeLayer具有以下優(yōu)勢:
速度快庄敛,內(nèi)存效率高(不用像CALayer一樣創(chuàng)建一個backing image)俗壹,不會被layer的bounds切邊,沒有像素
單獨(dú)設(shè)置layer每個角的圓角
//define path parameters
CGRect rect = CGRectMake(50, 50, 100, 100);
CGSize radii = CGSizeMake(20, 20);
UIRectCorner corners = UIRectCornerTopRight | UIRectCornerBottomRight | UIRectCornerBottomLeft;
//create path
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:corners cornerRadii:radii];
使用CAShapeLayer的一個列子
func setShapeLayer() {
//生成路徑
let path = UIBezierPath()
path.moveToPoint(CGPoint(x: 100,y: 100))
path.addLineToPoint(CGPoint(x: 130,y: 200))
path.addLineToPoint(CGPoint(x: 50,y: 130))
path.addLineToPoint(CGPoint(x: 150,y: 130))
path.addLineToPoint(CGPoint(x: 70,y: 200))
path.closePath()
//生成shape layer并且設(shè)置渲染屬性
let shapeLayer = CAShapeLayer()
shapeLayer.strokeColor = UIColor.redColor().CGColor
shapeLayer.fillColor = UIColor.clearColor().CGColor
shapeLayer.lineWidth = 5
shapeLayer.path = path.CGPath
//添加到view中
view.layer.addSublayer(shapeLayer)
}
-
CAGradientLayer
CAGradientLayer可以用來形成兩種或者多種顏色之間的平滑漸變藻烤。
-
基礎(chǔ)漸變
func addGradientLayer() { let gradientLayer = CAGradientLayer() gradientLayer.frame = CGRectMake(100, 100, 100, 100) gradientLayer.colors = [UIColor.redColor().CGColor, UIColor.blueColor().CGColor] gradientLayer.startPoint = CGPointMake(0, 0) gradientLayer.endPoint = CGPointMake(1, 1) view.layer.addSublayer(gradientLayer)
}
效果:
- 分段漸變
默認(rèn)的color在漸變中都是均勻分布的绷雏,我們可以使用locations屬性來控制每個color的分布范圍。
locations是一個范圍的數(shù)組怖亭,每個值都是在{0-1}的范圍中涎显,代表對應(yīng)colors數(shù)組中顏色在漸變中的范圍(所以,如果提供了locations這個屬性兴猩,即必須保證locations的數(shù)量與colors的一致)期吓。
func addMultipartGradientLayer() {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = CGRectMake(100, 300, 100, 100)
gradientLayer.colors = [UIColor.redColor().CGColor,
UIColor.blueColor().CGColor,
UIColor.greenColor().CGColor]
gradientLayer.startPoint = CGPointMake(0, 0.5)
gradientLayer.endPoint = CGPointMake(1, 0.5)
gradientLayer.locations = [0.2,0.5,0.8]
view.layer.addSublayer(gradientLayer)
}
效果:
locations屬性可以用來動畫,利用CAShapeLayer作為CAGradientLayer的mask倾芝,可以做出文字顏色漸變效果的動畫讨勤,大家可以自己去試試。
-
CAEmitterLayer
CAEmitterLayer是一個可以用來創(chuàng)建實(shí)時粒子動畫(比如煙霧晨另,火焰潭千,雨)效果的高性能的粒子引擎。
CAEmitterLayer作為CAEmitterCell的一個容器借尿,我們需要創(chuàng)建一個或者多個CAEmitterCell對象作為粒子的模板刨晴,CAEmitterLayer負(fù)責(zé)基于這些模板創(chuàng)建一個粒子流。
CAEmitterCell跟CALayer很像路翻,也有一個contents的屬性狈癞,還有其它很多的屬性,大部分都是用來控制粒子外觀的帚桩。
func addEmitterLayer() {
let emitter = CAEmitterLayer()
emitter.frame = CGRectMake(100, 100, 100, 100)
emitter.renderMode = kCAEmitterLayerPoint
emitter.emitterPosition = view.center
let cell = CAEmitterCell()
cell.contents = UIImage(named: "emiter")?.CGImage
cell.birthRate = 150
cell.lifetime = 5.0
cell.alphaSpeed = -0.4
cell.velocityRange = 50
cell.emissionRange = CGFloat(M_PI*2.0)
cell.color = UIColor(red: 1, green: 0.5, blue: 0.1, alpha: 1.0).CGColor
emitter.emitterCells = [cell]
view.layer.addSublayer(emitter)
}
效果:
CAEmitterCell的屬性一般分為以下幾類 :
- 粒子的某個屬性的開始值 : 比如color指定的將被用來與contents里面的image生成混合顏色
- 某個會在粒子之間產(chǎn)生變化的值的范圍 : 比如emissionRange代表的是粒子發(fā)射的范圍
- 某些會隨著時間改變的值 : 比如alphaSpeed,表示的是alpha每秒鐘的增量亿驾,設(shè)置成負(fù)數(shù)的時候,可以產(chǎn)生談出的效果
總結(jié):
特殊的CALayer還有很多種账嚎,包括CALayer的特性也還有很多莫瞬,因?yàn)槲乙策€沒讀完這本書,所以后面再加入吧郭蕉,不過大家可以去我上面提到的中文翻譯完整地址去看看疼邀,我這里面只是提到了書中很小的一部分,是我自己覺得對我來說挺有用的一些方面召锈,總之我覺得 《iOS Core Animation: Advanced Techniques 》這本書非常好旁振,希望有興趣學(xué)習(xí)動畫的同學(xué)可以去看看,強(qiáng)力推薦??。