這是 Core Animation 的系列文章圾叼,介紹了 Core Animation 的用法,以及如何進行性能優(yōu)化瘫拣。
Core Animation 是一個令人誤解的名字俄占,會被誤認為只能用來做動畫管怠。實際上,Core Animation 是 iOS颠放、macOS上的圖形渲染和動畫的基礎設施排惨,是一個合成引擎(Compositing Engine),以盡可能快的速度將屏幕上的不同視覺內(nèi)容進行合成碰凶。不同內(nèi)容存儲在不同圖層(layer)暮芭,層次結構中的 layer 構成圖層樹(layer tree)。Layer tree 構成UIKit
及屏幕上所有內(nèi)容的基礎欲低。
使用 Core Animation 創(chuàng)建動畫時辕宏,只需配置一些動畫參數(shù)(如起點、終點)砾莱,并告訴 Core Animation 開始執(zhí)行瑞筐,其余工作由 Core Animation 完成。動畫過程中每一幀的繪制都由 Core Animation 完成腊瑟,Core Animation 將繪制工作交由 GPU 以實現(xiàn)硬件加速聚假。這種自動借助硬件加速功能可以實現(xiàn)高幀率、流暢的動畫效果闰非,不會給 CPU 造成負擔或影響 app 運行速度膘格。
如果你在開發(fā) iOS app,那么你就在使用 Core Animation财松。如果你在開發(fā) macOS app瘪贱,你也可以很方便的使用 Core Animation纱控。Core Animation 位于AppKit
、UIKit
之下菜秦,集成于 Cocoa甜害、CocoaTouch 工作流中。Core Animation 具有擴展 app 視圖功能的接口球昨,并為動畫提供更精細化控制尔店。
1. 基礎信息
Core Animation 是對 app 視圖和其它視覺元素進行動畫處理的通用系統(tǒng)。Core Animation 不是為了替代 view主慰,而是與 view 集成闹获,提供更好的性能和動畫效果。Core Animation 通過將 view 內(nèi)容緩存為位圖河哑,以便 GPU 可以直接管理避诽。在某些情況下,由于 Core Animation 的緩存特性璃谨,你可能需要重新考慮如何展示沙庐、管理 app 內(nèi)容,但多數(shù)情況下佳吞,你在沒有意識到的情況時就已在使用 Core Animation 了拱雏。除了緩存視圖內(nèi)容,Core Animation 還提供了指定視覺內(nèi)容底扳,將該內(nèi)容與視圖集成铸抑,并應用動畫的方法。
使用 Core Animation 為 app 的視圖和視覺對象進行動畫處理衷模,大多數(shù)修改與視覺對象的屬性有關鹊汛。例如,可以使用 Core Animation 為視圖的位置阱冶、大小刁憋、不透明度進行動畫處理。當進行此類更改時木蹬,Core Animation 將在屬性當前值和指定值之間進行動畫處理至耻。
如果你為 iOS、macOS 開發(fā)過app镊叁,你就會熟悉 view 的概念尘颓。view 是一個用以展示內(nèi)容(如圖片、文本晦譬、視頻)的矩形對象疤苹,可以截取用戶輸入(如鼠標點擊、手勢觸摸)蛔添。view 可以嵌套使用構建復雜層級痰催,每個 view 管理子視圖的位置。下面是一個常見的視圖層級:
在 iOS 中迎瞧,view 都繼承自同一個基類UIView
夸溶,UIView
可以處理觸摸事件,支持 Core Graphics 繪制凶硅、仿射變換缝裁、淡入淡出動畫等。
UIView
本身并未處理多數(shù)這些任務足绅。渲染、動畫都由 Core Animation 的CALayer
類處理。
1.1 CALayer
CALayer
在概念上和UIView
很像绰咽,也是矩形對象嚷掠,可以排布為層級樹、包含內(nèi)容首量、管理子圖層的位置壮吩。CALayer
的屬性、方法提供了執(zhí)行動畫加缘、仿射變換的功能鸭叙。與UIView
相比,CALayer
缺少的主要功能是響應用戶交互事件拣宏。
CALayer
盡管提供了判斷點擊位置是否在 layer 內(nèi)的方法沈贝,但其并不清楚具體的響應鏈。
1.2 平行層級關系
每個UIView
有一個CALayer
類型的layer
屬性勋乾,被稱為backing layer宋下。View 負責創(chuàng)建、管理 layer辑莫,以確保子視圖添加杨凑、移除時,其關聯(lián)的圖層也在相應層級中進行同樣操作摆昧。
屏幕內(nèi)容的顯示和動畫都是由 backing layer 負責的撩满,UIView
僅僅是它的封裝,提供一些針對 iOS 的功能(如響應手勢事件)和核心動畫底層功能的高級封裝绅你。
為什么 iOS 有兩個平行層級的UIView
和CALayer
伺帘?為什么不使用一個層級處理所有事件?同時使用UIView
和CALayer
是為了職責分離忌锯,避免代碼重復伪嫁。iOS 和 macOS 中事件和用戶交互的處理邏輯有很多不同。多點觸摸的觸控屏與鼠標偶垮、鍵盤控制的完全不同张咳,這也是 iOS 使用UIKit
的UIView
帝洪,macOS 使用AppKit
的NSView
的原因,它們功能相同脚猾,但實現(xiàn)方式不同葱峡。
在 iOS 和 macOS 中,都有渲染龙助、布局砰奕、動畫這些共同概念,將這些基礎功能獨立到 Core Animation framework提鸟,可以在 iOS军援、macOS 間共享代碼,方便 Apple 系統(tǒng)開發(fā)人員開發(fā)系統(tǒng)称勋,以及第三方開發(fā)者開發(fā)跨平臺應用胸哥。
實際中,這里不是兩個平行層級赡鲜,而是四個平行層級烘嘱,各自執(zhí)行不同功能。層級如下:
- 視圖層級(View tree)蝗蛙。
- 圖層樹(Layer tree)蝇庭。
- 呈現(xiàn)樹(Presentation Tree)。
- 渲染樹(Render Tree)捡硅。
2. 圖層動畫
2.1 Layer 提供了繪制哮内、動畫基礎功能
Layer 對象是在3D空間中組織的2D畫面,是核心動畫的核心壮韭。與 view 類似北发,layer 管理幾何坐標、內(nèi)容和視覺屬性信息喷屋。
2.1.1 基于圖層的繪圖模型
大部分 layer 并不執(zhí)行繪制工作琳拨。相反,layer 捕獲 app 提供的內(nèi)容屯曹,并緩存為位圖狱庇。隨后修改 layer 屬性時,只需修改該 layer 對象的狀態(tài)信息恶耽。當修改會觸發(fā)動畫時密任,Core Animation 將 layer 的位圖和狀態(tài)信息傳遞給 GPU,GPU 根據(jù)傳入信息渲染位圖偷俭。使用硬件操控位圖比使用軟件快很多浪讳。
Core Animation 繪制內(nèi)容流程如下:
這里操縱的是靜態(tài)位圖,基于 layer 繪制與基于 view 繪制技術有著明顯不同涌萤。使用基于 view 的繪制時淹遵,改變 view 屬性會導致調(diào)用draw(_:)
方法重繪視圖口猜,這種重繪在主線程使用 CPU 進行,因此非常昂貴透揣。Core Animation 通過在硬件中操控緩存的位圖避免這些昂貴開銷济炎。
2.1.2 基于圖層的動畫
Layer 的數(shù)據(jù)和狀態(tài)信息與該 layer 在屏幕上的視覺呈現(xiàn)是分離的。這種解耦可以讓 Core Animation 將動畫從原來狀態(tài)逐步變更為新狀態(tài)淌实。
下圖顯示了部分類型動畫:
動畫過程中,核心動畫自動在硬件中完成每一幀的繪制猖腕。你只需指定開始拆祈、結束位置,Core Animation 負責其他所有工作倘感。也可以指定動畫時間信息或其他參數(shù)放坏,如果你沒有提供這些信息,Core Animation 會采用默認值老玛。
2.2 可動畫屬性
CALayer
中的某些可動畫屬性與UIView
中可動畫屬性對應淤年,如frame
、position
蜡豹、opacity
麸粮。
2.2.1 Position and size
修改 layer 的position
、size
镜廉、transform
弄诲,會同等修改關聯(lián)的 view,與直接修改 view 中這些屬性動畫一樣娇唯。
-
bounds
:以動畫形式修改邊框齐遵。 -
position
:在父圖層中,以動畫形式修改圖層位置塔插。也可以通過position.x
梗摇、position.y
只修改一個維度位置。 -
transform
:移動想许、縮放伶授、旋轉(zhuǎn)圖層。使用 layer 的transform
可以在3D空間使用動畫流纹,view 屬性動畫不能使用3D動畫谎砾。
CALayer
可以單獨使用,使用以下方法添加到 view:
let layer = CALayer()
layer.position = view.layer.position
layer.bounds = CGRect(x: 0, y: 0, width: width * 2, height: width * 2)
layer.backgroundColor = UIColor.darkGray.cgColor
view.layer.addSublayer(layer)
使用以下代碼修改 layer 的bounds
捧颅、position.y
景图、transform
:
layer.bounds = CGRect(x: 0, y: 0, width: width * 2, height: width * 2)
layer.position.y = layer.position.y + width
layer.transform = CATransform3DMakeRotation(.pi / 4.0, 0, 0, 1)
修改后效果如下:
雖然
bounds
、position
類型都是CGFloat
碉哑,但使用時應盡可能使用整數(shù)挚币。因為不能把邊界放到一個像素的中間或者三分之一等位置亮蒋,也就是像素不能再被分割。遇到小數(shù)時妆毕,設備會盡量模擬這種效果慎玖,但會耗費性能。
2.2.2 Border
通過修改圖層的 border笛粘,可以實現(xiàn)動畫修改顏色趁怔、寬度、拐角半徑薪前。
-
borderColor
:修改 border 顏色润努。 -
borderWidth
:當值大于0時,layer 使用borderColor
繪制邊框示括。繪制時铺浇,從bounds
向內(nèi)偏移borderWith
寬度繪制。邊框覆蓋在contents
和子圖層之上垛膝,包含cornerRadius
屬性效果鳍侣。borderWidth
默認值為0.0。 -
cornerRadius
:當值大于0時吼拥,layer 開始使用圓角裁剪背景倚聚。默認情況下,cornerRadius
不裁剪contents
凿可,只對backgroundColor
和 border有效秉沼。當masksToBounds
屬性為true
時,contents
會被裁減為圓角矿酵。cornerRadius
默認值為0.0唬复。
cornerRadius
效果如下:
修改方法如下:
layer.borderColor = UIColor.orange.cgColor
layer.borderWidth = 5
layer.cornerRadius = 15
效果如下:
Border 是跟隨著圖層邊界變化的,而不是圖層里面的內(nèi)容全肮。如果子圖層超過了邊界敞咧,邊框仍然會沿著圖層的邊界繪制出來。超出圖層邊框的content
和子圖層辜腺,會顯示在 border 之下:
2.2.3 Shadow
可以修改shadow
的以下屬性:
-
shadowOffset
:layer 陰影偏移量休建。默認為(0.0, -3.0)。 -
shadowOpacity
:陰影的不透明度评疗。shadowOpacity
值范圍是0.0(透明测砂,即不顯示陰影)至1.0(不透明)。該值默認為0.0百匆。 -
shadowPath
:修改 layer 的陰影形狀砌些。shadowPath
屬性默認值為nil
,這時采用標準的陰影形狀。如果為shadowPath
屬性指定了值存璃,layer 會采用該值生成陰影仑荐。path 定義了陰影的邊框,內(nèi)容采用shadowOpacity
纵东、shadowColor
粘招、shadowRadius
合成。 -
shadowRadius
:渲染 layer 陰影的模糊半徑偎球。默認值為3.0洒扎。
使用shadowOffset
方法如下:
layer.shadowOffset = CGSize(width: 15, height: 20)
layer.shadowOpacity = 0.5
效果如下:
如果沒有設置
backgroundColor
,shadow 會顯示到content
上衰絮;如果有backgroundColor
袍冷,shadow 顯示到content
下,也就是 border 內(nèi)不顯示岂傲,只在邊框外顯示陰影难裆。
使用shadowPath
不會觸發(fā)離屏渲染子檀,性能更好镊掖。方法如下:
layer.shadowOpacity = 0.8
let shadowHeight: CGFloat = 20
let shadowPath = CGPath(ellipseIn: CGRect(x: -shadowHeight,
y: layer.bounds.size.height,
width: layer.bounds.width + shadowHeight * 2,
height: shadowHeight),
transform: nil)
layer.shadowPath = shadowPath
效果如下:
使用
masksToBounds
會裁剪掉超出邊框所有內(nèi)容,包含陰影褂痰∧督可以使用兩個疊加的 layer 實現(xiàn)陰影效果。
2.2.4 Contents
以下屬性影響 layer contents 渲染:
-
contents
:將 TIFF 或 PNG 數(shù)據(jù)賦值給contents
屬性缩歪。 -
mask
:用于遮蓋圖層可見內(nèi)容的形狀或圖像归薛。 -
opacity
:不透明度。值范圍是0.0(透明的)至1.0(不透明的)匪蝙。超出范圍的值會被限制為最小值或最大值主籍。opacity
屬性默認值為1.0。
contents
屬性用法如下:
layer.contents = UIImage(named: "Ball")?.cgImage
layer.cornerRadius = 20
layer.masksToBounds = true
效果如下:
上述這些屬性只是部分可動畫屬性逛球,CALayer
的子類可能有額外可動畫屬性千元。Animatable Properties列出了所有可以使用動畫效果的屬性。
直接修改 layer 的屬性颤绕,會使用隱式動畫(implicit animation)幸海。隱式動畫采用默認時間信息和其他屬性執(zhí)行動畫。UIView
默認禁用了 layer 動畫奥务,只在 animation block 內(nèi)開啟了動畫物独。因此,直接修改單獨 layer 的上述屬性氯葬,會以動畫形式執(zhí)行挡篓。只有在 animation block 中修改視圖屬性,才會開啟動畫帚称。
3. Layer 定義了自身的幾何信息
Layer 的功能之一就是管理其內(nèi)容的幾何信息瞻凤。幾何信息包含bounds
憨攒、position
、旋轉(zhuǎn)阀参、縮放和變換等肝集。與 view 類似,layer 也使用bounds
蛛壳、position
信息決定內(nèi)容位置杏瞻。此外,Layer 還具有 view 沒有的屬性衙荐,如anchorPoint
捞挥。anchorPoint
定義了 layer 的錨點。
3.1 Layer 使用兩種坐標系統(tǒng)
Layer 同時使用點坐標系(point-based coordinate system)和單位坐標系(unit coordinate system)指定內(nèi)容位置忧吟。根據(jù)位置信息類型決定使用哪種坐標系砌函。當映射到屏幕或相對于其他圖層時(如position
),使用點坐標系溜族。當值不應綁定到屏幕時(如anchorPoint
屬性)讹俊,即相對于其它值的,使用單位坐標系煌抒。
Point-based coordinate 最常見用途是指定 layer 的大小和位置仍劈,bounds
屬性指定 layer 在屏幕上的大小,position
屬性指定 layer 相對父圖層的位置寡壮。雖然 layer 也有frame
屬性贩疙,但frame
是從bounds
和position
派生而來,很少使用况既。
bounds
和frame
的方向因平臺而已这溅。在 iOS 中,bounds
原點在左上角棒仍;在 macOS 中悲靴,bounds
原點在左下角。
在上圖中降狠,position
位于 layer 的中心对竣,position
屬性隨anchorPoint
而變。anchorPoint
默認值是(0.5, 0.5)榜配,表示 layer 矩形的中心否纬。所有對 view 的操作均以該點為中心執(zhí)行。例如蛋褥,anchorPoint
為默認值時临燃,旋轉(zhuǎn)圍繞中心進行。改變anchorPoint
后,旋轉(zhuǎn)以新設置點為中心進行膜廊。
Anchor point 是少數(shù)幾個使用單位坐標系的屬性之一乏沸。單位坐標系值范圍是0.0至1.0。例如爪瓜,在x軸上蹬跃,左側為0.0,右側為1.0铆铆。在y軸上蝶缀,單位坐標系的方向因平臺而異。
CAGradientLayer
的startPoint
薄货、stopPoint
也使用單位坐標系翁都。
3.2 Anchor point 影響操作幾何信息
對幾何信息的操作都以 anchor point 為中心進行,使用anchorPoint
屬性獲取 layer 的 anchor point谅猾。當操作position
柄慰、transform
時,anchor point 的影響尤為明顯税娜。
對 layer 添加旋轉(zhuǎn)變換后坐搔,旋轉(zhuǎn)以 anchor point 為中心執(zhí)行。因為anchorPoint
默認位于圖層中心巧涧,旋轉(zhuǎn)圍繞中心進行薯蝎,但可以通過修改anchorPoint
改變旋轉(zhuǎn)的中心遥倦,如下圖所示:
設置 layer 的anchorPoint
為(0.0, 0.0)谤绳,并進行旋轉(zhuǎn):
layer.anchorPoint = CGPoint(x: 0.0, y: 0.0)
layer.transform = CATransform3DMakeRotation(.pi / 4.0, 0, 0, 1)
效果如下:
可以指定
anchorPoint
的x、y值小于0袒哥、大于1缩筛,使anchorPoint
置于圖層之外。
視圖的frame
堡称、bounds
和center
屬性僅僅是存取方法瞎抛。操縱視圖的frame
時,實際上是在改變位于視圖下方CALayer
的frame
却紧,不能夠獨立于圖層之外改變視圖的frame
桐臊。
對于視圖或圖層來說,frame
并不是一個非常清晰的屬性晓殊,它其實是一個虛擬屬性断凶,由bounds
、position
和transform
計算得出巫俺。任何一個值變化认烁,frame
都可能發(fā)生變化。改變frame
同樣可能會影響到它們的值。
對圖層做變換的時候(如旋轉(zhuǎn)却嗡、縮放)舶沛,frame
實際上代表了覆蓋在圖層旋轉(zhuǎn)之后整個軸對齊的矩形區(qū)域。此時窗价,frame
的寬高可能和bounds
的寬高不一致如庭。
3.3 從三個維度操縱圖層
每個 layer 有兩個變換矩陣(Transform Matrices),變換矩陣可以用以操縱 layer 和 contents撼港。CALayer
的transform
屬性指定圖層及其子圖層的變換矩陣柱彻。通常使用transform
對圖層進行臨時縮放、旋轉(zhuǎn)餐胀、偏移哟楷。sublayerTransform
定義子圖層額外的變換矩陣。
Transform 通過將坐標乘以矩陣值否灾,得出變換后的坐標卖擅。因為 Core Animation 的值由三個維度指定,每個坐標點有四個值墨技,這些點需乘以4*4矩陣惩阶。Core Animation 提供了縮放、偏移扣汪、旋轉(zhuǎn)的矩陣断楷。
下圖顯示了一些常見變換矩陣配置。任何矩陣乘以 transform identify崭别,都會返回相同坐標冬筒。對于其他轉(zhuǎn)換,根據(jù)改變類型選取矩陣茅主。例如舞痰,要偏移x軸,提供非零的tx
诀姚,值為0的ty
响牛、yz
矩陣。對于旋轉(zhuǎn)赫段,提供合適的 sine 和 cosine 值和旋轉(zhuǎn)角度呀打。
4. 圖層樹反應不同方面的動畫狀態(tài)
使用 Core Animation 的 app 具有三組圖層對象,每組對象具有不同作用糯笙。
- Model layer tree:app 與 model layer tree 交互最為頻繁贬丛,存儲了動畫的目標值。
- Presentation tree:動畫進行過程中此刻的值炬丸,不要嘗試修改 presentation tree 中的值瘫寝。用該對象讀取動畫當前位置的值蜒蕾,可以用以從當前位置開啟新的動畫。
- Render tree:真正執(zhí)行動畫的圖層焕阿,為 Core Animation 私有咪啡。
每一組圖層都組織為一個層次結構。對于所有圖層都關聯(lián)視圖的應用暮屡,圖層和視圖的初始層次結構相同撤摸。想要避免視圖相關開銷,且確定不需要視圖相關功能時褒纲,可以單獨創(chuàng)建准夷、添加圖層。下圖顯示了一個 iOS app 分解圖莺掠。窗口包含一個 content view衫嵌,content view 包含 button view 和兩個 layer 對象。layer 層級如下:
Layer tree 中的對象彻秆,在 presentation tree 和 render tree 中有對應對象楔绞。App 主要使用 layer tree 中圖層,有時使用 presentation tree 中對象唇兑。通過 layer tree 對象的presentation()
屬性獲取 presentation tree 中對應的 layer酒朵,這樣可以獲取到動畫此刻的值。如下所示:
只有動畫正在進行時扎附,才可以使用
presentation()
屬性獲取 presentation tree 中對象蔫耽。其他時刻調(diào)用presentation()
可能產(chǎn)生無法預期的結果。Layer tree 對象的值代表最后一次用代碼設置的值留夜。
5. Layer 和 View 關系
在 iOS 中匙铡,每個UIView
都由 Core Animation 的CALayer
支持。因此香伴,使用UIView
時就在使用CALayer
慰枕。這一點與 macOS 上的NSView
不同具则,NSView
在 Core Animation 之前就已存在即纲。UIView
是對NSView
的輕量級包裝。
View 特點如下:
- 復雜層級結構博肋,自動布局低斋。
- 與用戶交互,響應鏈匪凡。
- 在主線程的 CPU 中執(zhí)行自定義繪制邏輯膊畴。
- 靈活、強大病游,有很多子類唇跨。
Layer 特點如下:
- 層級結構簡單稠通,渲染速度快,不支持自動布局买猖。
- 沒有響應鏈的開銷改橘。
- 在 GPU 中渲染。
- 不太靈活玉控,子類偏少飞主。
直接使用CALayer
并不能獲得明顯性能提升。相比使用UIView
高诺,使用CALayer
可以開發(fā)同時在 iOS碌识、macOS 上運行的app。UIView
和NSView
有很多不同虱而,但CALayer
在 iOS筏餐、macOS 上幾乎相同。
UIView
的 animation 可以滿足大部分動畫需求牡拇,遇到UIView
不能實現(xiàn)的復雜動畫時胖烛,需要借助 Core Animation實現(xiàn)。在 iOS 10 中诅迷,Apple在UIKit
中增加了UIViewPropertyAnimator類佩番,使用該類實現(xiàn)的動畫可以在結束前進行暫停、恢復罢杉、停止等操作趟畏。UIViewPropertyAnimator
借助物理引擎,實現(xiàn)了碰撞滩租、彈性等效果赋秀。
Demo名稱:CoreAnimation
源碼地址:https://github.com/pro648/BasicDemos-iOS/tree/master/CoreAnimation
參考資料:
- What are the differences between a UIView and a CALayer?
- iOS Brownbag: View vs. Layers (including Clock Demo)
- Core Animation Basics
- 圖層幾何學
- Always use integral numbers for the width and height of your layer.
歡迎更多指正:https://github.com/pro648/tips
本文地址:https://github.com/pro648/tips/blob/master/sources/CoreAnimation基本介紹.md