1.CALayer 與 UIView 的區別:
它們有一些方法和屬性用來做動畫和變換。和UIView最大的不同是CALayer不處理用戶的交互。
CALayer并不清楚具體的響應鏈(iOS通過視圖層級關系用來傳送觸摸事件的機制),于是它并不能夠響應事件,即使它提供了一些方法來判斷是否一個觸點在圖層的范圍之內
除了視圖層級和圖層樹之外,還存在呈現樹和渲染樹
我們已經證實了圖層不能像視圖那樣處理觸摸事件,那么他能做哪些視圖不能做的呢?這里有一些UIView沒有暴露出來的CALayer的功能:
陰影,圓角,帶顏色的邊框
3D變換
非矩形范圍
透明遮罩
多級非線性動畫
一個視圖(UIView)只有一個相關聯的圖層(layer)(自動創建),同時它也可以支持添加無數多個子圖層
2.CALayer 的 寄宿圖
CALayer類能夠包含一張你喜歡的圖片,這一章節我們將來探索CALayer的寄宿圖(即圖層中包含的圖)
contents屬性:
在實踐中,如果你給contents賦的不是CGImage,那么你得到的圖層將是空白的.
事實上,你真正要賦值的類型應該是CGImageRef,它是一個指向CGImage結構的指針。UIImage有一個CGImage屬性,它返回一個"CGImageRef",如果你想把這個值直接賦值給CALayer的contents,那你將會得到一個編譯錯誤。因為CGImageRef并不是一個真正的Cocoa對象,而是一個Core Foundation類型。
盡管Core Foundation類型跟Cocoa對象在運行時貌似很像(被稱作toll-free bridging),他們并不是類型兼容的,不過你可以通過bridged關鍵字轉換。如果要給圖層的寄宿圖賦值,你可以按照以下這個方法:
layer.contents = (__bridge id)image.CGImage;
.
注意:Core Foundation類型與Cocoa對象運行時很像,但是不一樣,可以通過(__bridge id)方法來轉換
contentsRect在app中最有趣的地方在于一個叫做image sprites(圖片拼合)的用法。
利用Interface Builder探測窗口 的 Stretching控制contentsCenter屬性
雖然-drawRect:方法是一個UIView方法,事實上都是底層的CALayer安排了重繪工作和保存了因此產生的圖片。
3.圖層幾何學
UIView有三個比較重要的布局屬性:frame,bounds和center,CALayer對應地叫做frame,bounds和position。為了能清楚區分,圖層用了“position”,視圖用了“center”,但是他們都代表同樣的值。
對于視圖或者圖層來說,frame并不是一個非常清晰的屬性,它其實是一個虛擬屬性,是根據bounds,position和transform計算而來,所以當其中任何一個值發生改變,frame都會變化。相反,改變frame的值同樣會影響到他們當中的值
hitTest:方法同樣接受一個CGPoint類型參數,而不是BOOL類型,它返回圖層本身,或者包含這個坐標點的葉子節點圖層。
4.coreAnimation并不是一個繪圖系統。它只是一個負責在硬件上合成和操縱應用內容的基礎構件。
CoreAnimation核心是圖層對象,圖層對象用于管理和操控你的應用內容。
圖層將捕獲的內容放到一副位圖中,圖形硬件能夠非常容易的操控你的位圖。
圖層被作為一種管理視圖內容的方式,但是你也可以創建標準的圖層,這取決于你自身的需要。
動畫分為:隱式動畫和顯示動畫
隱式動畫是一種從舊屬性值動畫到新屬性值的動畫形式。
******基于圖層的繪圖模型******
對基于視圖的繪圖,對視圖的改變經常會觸發調用視圖的drawRect:方法以重繪視圖內容。但是此種方式的代價相對較高,因為它是CPU在主線程上的操作。Core Animation通過盡可能的使用圖形硬件操縱緩存后的位圖來避免了這種開銷,從而完成相同或相似的效果。
****基于圖層的動畫****
可在圖層上執行的動畫類型:移動,縮放,旋轉,透明度,圓角半徑,背景顏色。
圖層對象定義了自己的幾何結構
****圖層使用兩種類型的坐標系統****
圖層利用基于點的坐標系統和單位坐標系統指定內容的布局,當指定的值是直接映射到屏幕或相對于其他圖層的坐標,圖層的position屬性,則使用基于點的坐標系統,與屏幕坐標不相關聯,則使用單位坐標。比如圖層的anchorPoint屬性。
錨點是使用單位坐標系統的屬性之一。
錨點影響幾何結構的操作
position屬性是相對于圖層的錨點被指定。并且任何你對圖層陰影的變換操作也是相對于錨點。
AnchorPoint相對于自身wh的百分比,0.0~1.0,position是相對于父視圖的坐標點。
5.CoreAnimation
作用不僅僅是做動畫。
在iOS端,我們看到的一切都是由Core Animation來完成的。
但Core Animation并不做實際的繪制渲染的工作,實際的繪制渲染工作由繪制渲染系統(GPU)來完成,Core Animation是一個中間層框架。
Core Animation為其它框架提供數據和傳遞數據,當然Core Animation也會對數據進行處理,像坐標,矩形計算,創建OpenGL紋理等。
iOS的整個顯示流程沒UIKit什么事,這跟Mac OS的處理是有所不同的,iOS的顯示是默認建立在Core Animation之上,顯示層的處理是通過Core Animation來完成,而Mac OS并不依賴于Core Animation。由于Core Animation在iOS端的位置,所以你如何使用Core Animation這將會對性能產生極大的影響。
Core Animation動畫有兩種形式,分別是顯式動畫和隱式動畫。
但不管以哪種形式實現動畫,都是基于CAAnimation來實現的,所以這兩種動畫形式的主要差異在于,是否顯式創建CAAnimation對象來實現動畫。當然它們還存在其它的差異,例如,隱式動畫是基于CABasicAnimation對來實現的,而顯式動畫可以有更多的選擇,可以是CABasicAnimation,CAKeyframeAnimation,CATransition等。
// 隱式動畫
var layer = CALayer.init()
layer.backgroundColor = UIColor.black.cgColor
// 顯示動畫
var animation = CABasicAnimation.init(keyPath: "backgroundColor") // 顯示創建CAAnimation對象
animation.fromValue = layer.backgroundColor
animation.toValue = UIColor.black.cgColor
layer.backgroundColor = UIColor.black.cgColor
layer.add(animation, forKey: nil)
對于CALayer
而言,隱式動畫是默認開啟的,只需要更改可動畫屬性就會觸發隱式動畫。但對于UIView
的Backing Layer
而言,隱式動畫是默認關閉的。當更改獨立CALayer
的可動畫屬性時,Core Animation
會為我們創建默認的動畫對象來實現相應的動畫,但Core Animation
又以什么作為標準來實現這些動畫呢?隱式動畫的大部份參數由事務(CATransaction
)來決定,其中包括,動畫時間,動畫緩沖等。
雖然隱式動畫的大部份參數由CATransaction
來決定,但我們奇怪地發現,在代碼中并沒有出現CATransaction
,WHY?這是由于CATransaction
和NSAutoreleasePool
一樣,也分為隱式CATransaction
和顯式CATransaction
,而CATransaction
對隱式動畫的管理方式與NSAutoreleasePool對內存的管理方式也十分相似,[CATransaction begin]
方法是CATransaction
的開始,而[CATransaction commit]
則是CATransaction
的結束,中間便是CATransaction
的作用域,需要把更改可動畫屬性的操作放在CATransaction
的作用域內,Core Animation
才會創建相應的隱式動畫。
[CATransaction begin];
// do something
[CATransation commit];
這個上面為CATransation的顯示創建。顯式創建CATransaction常常被用于關閉隱式動畫(獨立的CALayer對象默認開啟隱式動畫,需要手動關閉)和調整動畫的時間。
隱式CATransaction是在RunLoop的一次Loop開始時begin,Loop結束時commit,正因為有隱式CATransaction的存在,所以當你更改獨立CALayer的可動畫屬性時,Core Animation會為此創建隱式動畫。CATransaction和NSAutoreleasePool一樣,可以嵌套處理。
不管是顯式動畫還是隱式動畫,動畫屬性的初始值要不同于目標值才會產生動畫。Core Animation動畫過程中,顯示在屏幕上的是呈現樹
的對象,而不是圖層樹
的對象。
6.UIKit動畫
UIKit動畫和Core Animation動畫是相關連的,它們的關系就好像NSOperation和GCD一樣,UIKit動畫是建立在Core Animation動畫之上的。
UIView對象的Backing Layer的隱式動畫是默認關閉的,如果想開啟需要使用UIKit的事務。
// 開啟UIKit隱身動畫
// 方式1
UIView.beginAnimations(nil, context: nil)
// do something
UIView.commitAnimations()
// 方式2
UIView.animate(withDuration: 0.25) {
// do something
}
UIKit事務繼承了Core Animation事務(CATransaction)的所有功能,并且在此基礎上做了許多擴展,像觸摸響應,更多的動畫對象等。
作為Core Animation動畫的一個抽象物,UIKit雖然提供了非常簡便的API來實現動畫,但UIKit動畫畢竟是Core Animation動畫的一個抽象物,能提供的效果還是很有限的。
7.Core Animation動畫與觸摸響應
當我們觸摸屏幕時,系統就會把這個觸摸事件傳遞給相應Application,Application接收到觸摸事件后,執行_UIApplicationHandleEventQueue()函數(這是一個Source0的回調),把觸摸事件轉換成UIEvent并開始傳遞和處理事件。
Application處理觸摸事件主要分為以下兩步:
1).獲取Hit-Test View。
2).尋找響應者處理事件。
響應鏈是由一組連接在一起的響應者組成,UIResponder是所有響應者的基類,響應者的類型不是唯一的,不同的系統事件對應著不同類型的響應者,而觸摸事件的響應者主要是UIApplication對象,UIViewController對象和UIView對象,UIApplication對象是響應鏈的第一個節點,也是最后一個響應事件的對象。
以上兩步操作由兩個不同的函數完成,_UIApplicationHandleDigitizerEvent()函數用于獲取Hit-Test View,[UIApplication sendEvent:]函數用于查找可以處理事件的響應者,當然這兩個函數是在_UIApplicationHandleEventQueue()中被調用的,調用關系如下圖。
_UIApplicationHandleDigitizerEvent()函數通過調用UIView對象的hitTest: withEvent:函數來獲取Hit-Test View。
一個UIWindow的hitTest: withEvent:函數返回值是當前Window的Hit-Test View,Hit-Test View是響應鏈中第一個被檢測是否能處理觸摸事件的對象。獲取Hit-Test View后,調用[UIApplication sendEvent:]函數從響應鏈中尋找響應者處理事件,當找到響應者處理觸摸事件后,本次觸摸事件處理結束(當然,你也可以通過重寫一些函數使事件繼續傳遞)。
即使不發生觸摸事件,我們也可以通過nextResponder來獲取上一級響應者。
UIKit在UIResponder對象關系確立的同時生成響應鏈,響應鏈與視圖的層級結構也是一一對應的,一般情況下,父視圖是子視圖的上一級響應者,當然,也有一些特殊的情況,像UIViewController根視圖的上一級響應者是UIViewController而不是父視圖。
正常情況下,判斷觸摸點是否在當前圖層樹對象的坐標系中用的是pointInside: withEvent:函數。也有一些特殊的情況,如果一個視圖正在實現Core Animation動畫且動畫過程中不允許交互(默認不交互),那么該視圖不再調用pointInside: withEvent:函數來判斷觸摸點,而是獲取動畫視圖的呈現樹對象,判斷觸摸點是否在該呈現樹對象的坐標系中。若Hit-Test View正在實現Core Animation動畫且動畫過程中不允許交互,那么本次觸摸事件將不處理且不再尋找響應者處理事件。
若視圖正在實現Core Animation動畫且動畫過程中允許交互(動畫過程中允許交互需要添加UIViewAnimationOptionAllowUserInteraction),那么不能調用pointInside: withEvent:函數來判斷觸摸點是否在當前圖層樹對象的坐標系中。
pointInside: withEvent:函數用于判斷的對象是圖層樹中的對象,而Core Animation動畫過程中,顯示在屏幕上的是呈現樹的對象,而不是圖層樹的對象,但由于圖層樹對象存儲的是目標值,而呈現樹對象存儲的是瞬時值。
呈現樹:Presentation Layer
圖層樹:Model Layer
8.定時動畫
除了Core Animation動畫,iOS動畫還有其它實現的方式,像定時動畫,手勢動畫等。
定時動畫與Core Animation動畫不同,定時動畫并不是基于CAAnimation來實現的,而是通過定時器的觸發不斷地改變圖層樹對象的UI屬性值來實現的,每次定時觸發所改變的UI屬性都會被Application發送到渲染服務進程,渲染服務進程就會把最新的圖層樹渲染到屏幕上。通過定時器的觸發不斷地重復這些步驟,最終在屏幕上形成了動畫。
就是通過timer不斷刷新view的frame
如果你想控制動畫的每一幀,完全自定義動畫的緩沖,那么定時動畫是一個非常不錯的選擇,就像ScrollView一樣,ScrollView的滾動就是一個定時動畫,正因為如此,所以TableView才可以實時更新Cell的內容并顯示出來。這種實時同步圖層樹的方式,既是定時動畫的優點,也是定時動畫的缺點,缺點在于一旦實時同步的操作時間過長就會出現屏幕掉幀的情況,最常見的例子就是TableView在滾動時出現卡屏掉幀。(原理:定時動畫)
Core Animation動畫VS定時動畫
不管以那種形式實現動畫,動畫都是一幀幀實現的,Core Animation動畫與定時動畫主要的不同點在于,Core Animation動畫是以CAAnimation對象為基礎的動畫,Application通過發送圖層樹和CAAnimation對象到渲染服務進程,由渲染服務進程完成動畫所需要的每一幀;
而定時動畫則是通過定時器的解發,不斷地改變圖層樹,Application把每次改變后的圖層樹發送到渲染服務進程進行屏幕的重新渲染。
一般情況下,Core Animation動畫會是更好的選擇,不僅是因為實現Core Animation動畫相對簡單,而且動畫的每一幀由渲染服務進程完成,Application有更多的空間來完成其它任務,提高CPU的效率;
當然,你也可以使用一些物理框架來實現定時動畫,自定義動畫的緩沖,實現更真實的效果。更多的時候是需要根據界面的情況來選擇實現動畫的形式,并沒有一種形式適用于任何情況,關鍵在于如何減少掉幀,保持界面流暢度。
一般情況下,Core Animation動畫會是更好的選擇,不僅是因為實現Core Animation動畫相對簡單,而且動畫的每一幀由渲染服務進程完成,Application有更多的空間來完成其它任務,提高CPU的效率;當然,你也可以使用一些物理框架來實現定時動畫,自定義動畫的緩沖,實現更真實的效果。更多的時候是需要根據界面的情況來選擇實現動畫的形式,并沒有一種形式適用于任何情況,關鍵在于如何減少掉幀,保持界面流暢度。
終結:
在iOS端,你在屏幕上看到的一切都是由Core Animation來完成的,當你打算往顯示方面進階時,你需要深入研究的是Core Animation而不是UIKit,就像Instruments里沒有UIKit測試工具一樣,雖然UIKit抽象了很多Core Animation的功能,但同時也帶來了很多限制,有時候你更需要的是一些自定義效果。