前言
? ? ? 相信大部份iOS開發者在實現一些基礎動畫時會感嘆動畫API的簡便及動畫效果的真實性,動畫是iOS的一大特色,Core Animation的存在使得我們實現一些基礎動畫變得十分簡單,本文將會講述iOS一些動畫的基礎知識,以及動畫與事件鏈沖突的處理。
目錄
-Core Animation
-Core Animation動畫
-UIKit動畫
-Core Animation動畫與觸摸響應
-定時動畫
-Core Animation動畫VS定時動畫
-總結
Core Animation
? ? ? 當提起Core Animation,可能有很多人的第一反應是,Core Animation是用來做動畫的,這個回答沒有錯,Core Animation確實是用來做動畫,而且提供了非常簡便的動畫API,但這里有一個誤區,很多人會認為Core Animation只是用來做動畫的,或者大家在網上找到的大部份跟Core Animation相關的資料都只是講述了如果使用Core Animation實現動畫,這對Core Animation的認知是很片面的,Core Animation不僅僅只有動畫,相反,動畫只是Core Animation的一小部份。
? ? ? 在iOS端,你在屏幕上看到的一切都是由Core Animation來完成的,但Core Animation并不做實際的繪制渲染的工作,實際的繪制渲染工作由繪制渲染系統來完成,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會是你進階的第一步,也是非常關鍵的一步。關于Core Animation,可以看《iOS Core Animation Advanced Techniques》這本書,不知道這本書是否有更新,因為我第一次接觸這本書已經是三年前了,即使沒有更新,也沒有關系,這并不影響書的質量,這本書可以讓你全面了解到Core Animation,當然你還可以查閱蘋果的官方文檔,或者網上的技術文,但個人推薦看專題書,因為專題書的全面性是網上的技術文無法給予的。
Core Animation動畫
? ? ? Core Animation動畫有兩種形式,分別是顯式動畫和隱式動畫,但不管以哪種形式實現動畫,都是基于CAAnimation來實現的,所以這兩種動畫形式的主要差異在于,是否顯式創建CAAnimation對象來實現動畫,當然它們還存在其它的差異,例如,隱式動畫是基于CABasicAnimation對來實現的,而顯式動畫可以有更多的選擇,可以是CABasicAnimation,CAKeyframeAnimation,CATransition等。
? ? ? 對于獨立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的顯式創建,顯式創建CATransaction常常被用于關閉隱式動畫(獨立的CALayer對象默認開啟隱式動畫,需要手動關閉)和調整動畫的時間。隱式CATransaction是在RunLoop的一次Loop開始時begin,Loop結束時commit,正因為有隱式CATransaction的存在,所以當你更改獨立CALayer的可動畫屬性時,Core Animation會為此創建隱式動畫。CATransaction和NSAutoreleasePool一樣,可以嵌套處理。
? ? ?需要注意的是,不管是顯式動畫還是隱式動畫,動畫屬性的初始值要不同于目標值才會產生動畫。Core Animation動畫過程中,顯示在屏幕上的是呈現樹的對象,而不是圖層樹的對象。
UIKit動畫
? ? ? 除了Core Animation的動畫API,蘋果還為我們準備了UIKit的動畫API。UIKit實現的動畫效果往往都可以滿足你對動畫的需求,而且實現代碼非常簡單,就像Core Animation的隱式動畫一樣,只需要在UIKit事務中修改相應可動畫屬性即可,所以,一般情況下UIKit動畫是你優先選擇的方式,但這里有一個誤區,網上有很多技術文在講述iOS動畫的時候,會把UIKit動畫和Core Animation動畫定義成兩種完全不同的處理方式,并且認為Core Animation動畫的性能會更優一些,但事實并非如此,UIKit動畫和Core Animation動畫是相關連的,它們的關系就好像NSOperation和GCD一樣,UIKit動畫是建立在Core Animation動畫之上的,下面我們來簡單地驗證一下。
? ? ? 一個簡單的平移動畫,只需要實現以上UIKit動畫代碼即可。
? ? ? 我們需要知道的是Core Animation層在UIKit動畫前后的變化,通過監聽animationView.layer.animationKeys來獲取相關信息。
? ? ? 從輸出的信息可以看到,UIKit動畫其內部實現是Core Animation動畫。UIView對象的Backing Layer的隱式動畫是默認關閉的,如果想開啟需要使用UIKit的事務。
? ? ? UIKit事務繼承了Core Animation事務(CATransaction)的所有功能,并且在此基礎上做了許多擴展,像觸摸響應,更多的動畫對象等。作為Core Animation動畫的一個抽象物,UIKit雖然提供了非常簡便的API來實現動畫,但UIKit動畫畢竟是Core Animation動畫的一個抽象物,能提供的效果還是很有限的。
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,hitTest: withEvent:函數簡潔的偽代碼如下。
? ? ? 一個UIWindow的hitTest: withEvent:函數返回值是當前Window的Hit-Test View,Hit-Test View是響應鏈中第一個被檢測是否能處理觸摸事件的對象。獲取Hit-Test View后,調用[UIApplication sendEvent:]函數從響應鏈中尋找響應者處理事件,當找到響應者處理觸摸事件后,本次觸摸事件處理結束(當然,你也可以通過重寫一些函數使事件繼續傳遞)。
? ? ? 跟很多開發者交流的時候發現一個誤區,很多人認為響應鏈是在觸摸事件發生時才生成的,但事實并非如此,若響應鏈在觸摸事件發生時才生成,那么只要不發生觸摸事件就不會存在響應鏈,但事實上,即使不發生觸摸事件,我們也可以通過nextResponder來獲取上一級響應者。UIKit在UIResponder對象關系確立的同時生成響應鏈,響應鏈與視圖的層級結構也是一一對應的,一般情況下,父視圖是子視圖的上一級響應者,當然,也有一些特殊的情況,像UIViewController根視圖的上一級響應者是UIViewController而不是父視圖(下圖是官方文檔的圖)。
? ? ? 就像圖8,圖9展示的那樣,一般情況下_UIApplicationHandleDigitizerEvent()函數獲取Hit-Test View的過程還是比較簡單的,但如果視圖加上了Core Animation動畫,那么獲取Hit-Test View的過程就變得相對復雜了。
? ? ? 正常情況下,判斷觸摸點是否在當前圖層樹對象的坐標系中用的是pointInside: withEvent:函數,但也有一些特殊的情況,如果一個視圖正在實現Core Animation動畫且動畫過程中不允許交互(默認不交互),那么該視圖不再調用pointInside: withEvent:函數來判斷觸摸點,而是獲取動畫視圖的呈現樹對象,判斷觸摸點是否在該呈現樹對象的坐標系中。若Hit-Test View正在實現Core Animation動畫且動畫過程中不允許交互,那么本次觸摸事件將不處理且不再尋找響應者處理事件。
? ? ? 若視圖正在實現Core Animation動畫且動畫過程中允許交互(動畫過程中允許交互需要添加UIViewAnimationOptionAllowUserInteraction),那么仍是調用pointInside: withEvent:函數來判斷觸摸點是否在當前圖層樹對象的坐標系中,且正常地處理觸摸事件,但當你去觸摸屏幕上正在實現Core Animation動畫的視圖時,就會出現以下情形。
? ? ? pointInside: withEvent:函數用于判斷的對象是圖層樹中的對象,而Core Animation動畫過程中,顯示在屏幕上的是呈現樹的對象,而不是圖層樹的對象,但由于圖層樹對象存儲的是目標值,而呈現樹對象存儲的是瞬時值,所以,屏幕上實際的圖層情況如下圖。
? ? ? 所以當你觸摸屏幕上正在實現Core Animation動畫的視圖時,出現了圖15所展示的情形,但在實際開發當中,我們需要的是正在實現Core Animation動畫的視圖也可以響應觸摸事件,為此,我們需要對觸摸坐標的進行識別,通過獲取當前觸摸點,判斷該點是否呈現樹對象坐標系中。
定時動畫
? ? ? 除了Core Animation動畫,iOS動畫還有其它實現的方式,像定時動畫,手勢動畫等。定時動畫與Core Animation動畫不同,定時動畫并不是基于CAAnimation來實現的,而是通過定時器的觸發不斷地改變圖層樹對象的UI屬性值來實現的,每次定時觸發所改變的UI屬性都會被Application發送到渲染服務進程,渲染服務進程就會把最新的圖層樹渲染到屏幕上。通過定時器的觸發不斷地重復這些步驟,最終在屏幕上形成了動畫。
? ? ? 上圖顯示的就是一個簡單的定時動畫,通過一秒60幀的頻率改變圖層樹來實現動畫效果。與Core Animation動畫的觸摸響應不同,定時動畫是通過不斷地改變圖層樹來實現的,屏幕上顯示的是圖層樹對象的實時值,所以實現定時動畫的視圖,可以正常響應觸摸事件而不需要做額外的處理。
? ? ? 如上圖所示,實現定時動畫的視圖可以正常響應觸摸事件。在實際開發當中,也許你從來沒有實現過定時動畫,但如果你想控制動畫的每一幀,完全自定義動畫的緩沖,那么定時動畫是一個非常不錯的選擇,就像ScrollView一樣,ScrollView的滾動就是一個定時動畫,正因為如此,所以TableView才可以實時更新Cell的內容并顯示出來。這種實時同步圖層樹的方式,既是定時動畫的優點,也是定時動畫的缺點,缺點在于一旦實時同步的操作時間過長就會出現屏幕掉幀的情況,最常見的例子就是TableView在滾動時出現卡屏掉幀。
? ? ? Facebook的POP動畫引擎就是實現為定時動畫,所以不建議在列表中使用,會容易出現動畫掉幀的情況。
Core Animation動畫VS定時動畫
? ? ? 不管以那種形式實現動畫,動畫都是一幀幀實現的,Core Animation動畫與定時動畫主要的不同點在于,Core Animation動畫是以CAAnimation對象為基礎的動畫,Application通過發送圖層樹和CAAnimation對象到渲染服務進程,由渲染服務進程完成動畫所需要的每一幀;而定時動畫則是通過定時器的解發,不斷地改變圖層樹,Application把每次改變后的圖層樹發送到渲染服務進程進行屏幕的重新渲染。
? ? ? 一般情況下,Core Animation動畫會是更好的選擇,不僅是因為實現Core Animation動畫相對簡單,而且動畫的每一幀由渲染服務進程完成,Application有更多的空間來完成其它任務,提高CPU的效率;當然,你也可以使用一些物理框架來實現定時動畫,自定義動畫的緩沖,實現更真實的效果。更多的時候是需要根據界面的情況來選擇實現動畫的形式,并沒有一種形式適用于任何情況,關鍵在于如何減少掉幀,保持界面流暢度。
總結
? ? ? 在iOS端,你在屏幕上看到的一切都是由Core Animation來完成的,當你打算往顯示方面進階時,你需要深入研究的是Core Animation而不是UIKit,就像Instruments里沒有UIKit測試工具一樣,雖然UIKit抽象了很多Core Animation的功能,但同時也帶來了很多限制,有時候你更需要的是一些自定義效果。