本文是《Core Animation Programming Guide》2013-01-28更新版本的譯文。本文略去了原文中關於OS X平台上Core Animation相關內容。因為原文的類型屬於編程指南,所以示例代碼並不多,更多的是理論層面的探討。所以譯文中加入了大量的示例代碼,以提高本文的可操作性。希望本文能夠對你有所幫助。
本文由海水的味道翻譯,轉載請注明譯者和出處,請勿用於商業用途!
關於Core Animation
Core Animation是iOS與OS X平台上負責圖形渲染與動畫的基礎設施。Core Animation可以動畫視圖和其他的可視元素,為你完成了動畫所需的大部分繪幀工作。你只需配置少量的動畫參數(如開始點位置和結束點的位置)即可施展Core Animation魔法。Core Animation將大部分實際的繪圖任務交給了圖形硬件來處理,圖形硬件會加速圖形渲染的速度。這種自動化的圖形加速技術讓動畫擁有更高的幀率並且更加的平滑,而且不會加重CPU的負擔而影響程序的運行速度。
如果你正在開發一個iOS應用,此刻你就已經運用了Core Animation;如果你開發的是OS X應用,只需小小的一點付出就可以贏得Core Animation的強大支持。如下圖所示,Core Animation位於AppKit和UIKit的底層。它被緊密的集成到了Cocoa和Cocoa Touch視圖工作流中。雖然被緊密的集成,Core Animation還是保留了部分用於擴展其功能的的接口,這些接口暴露給了應用的視圖。使用這些接口能讓你更細粒度地控制應用中的動畫。
你可能從不需要直接使用Core Animation,但是當你使用Core Animation,你應該了解Core Animation是程序的基礎設施中的一部分。
Core Animation管理着你的應用內容
Core Animation自身並不是一個繪圖系統。它只是一個負責在硬件上合成和操縱應用內容的基礎構件。Core Animation的核心是圖層對象,圖層對象用於管理和操控你的應用內容。圖層將捕獲的內容放到一副位圖中,圖形硬件能夠非常容易的操控你的位圖。在大部分應用中,圖層被作為一種管理視圖內容的方式,但是你也可以創建標准的圖層,這取決於你自身的需要。
更改圖層屬性會產生動畫
你使用Core Animation創建的大部分動畫都包含對圖層屬性的配置。像視圖一樣,圖層對象也具有frame、origin、size、opacity、transform以及許多其他面向可視的屬性(如backgroundColor)。大部分這些屬性的值發生了變化都將會觸發隱式動畫被創建。隱式動畫是一種從舊屬性值動畫到新屬性值的動畫形式。如果需要全面掌控動畫行為,你可以考慮使用顯式動畫這些屬性。
圖層的層級結構有層次結構的圖層之間存在這父子關系。圖層的層次結構會影響到圖層的可見區域。可見區域的管理與視圖的管理方法相似。添加到視圖上的圖層的層次結構和視圖的層次結構相似。你可以向已有的圖層結構中插入新的圖層以擴展視圖的可視內容。
使用動作對象改變圖層的默認行為
通過動作對象可以做到隱式的圖層動畫。動作對象是實現了一個預定義接口的常規對象。Core Animation使用動作對象實現與圖層關聯的常規的默認動畫集合。你可以創建屬於你自己的動作對象,以實現自定義的動畫或者實現其他行為類型,然后你將動作對象賦值給圖層的某一屬性。當屬性發生變化,Core Animation檢索你的動作對象並告訴動作對象執行對應的動作。
如何使用該文檔
該文檔為那些需要更全面的控制動畫效果或打算利用圖層提升繪圖性能的開發者。該文檔也介紹了iOS和OS X對圖層和視圖的合成。圖層和視圖的合成在iOS和OS X平台上是不同的,理解這些不同對於創建高效的動畫至關重要。
預備知識
你應該了解你的目標平台的視圖架構,以及熟悉如何創建基於視圖的動畫。如果你還未到該階段,請閱讀以下兩篇文檔:
對於iOS應用開發,你應該理解《iOS視圖編程指南》中關於視圖架構的介紹。
對於OS X應用開發,你應該理解《視圖編程指南》中關於視圖架構的介紹。
Core Animation基礎
Core Animation為動畫視圖和其他可視元素提供了一個通用的系統。Core Animation並不是視圖的替代品,相反,它是一種和視圖相集成的技術。由於位圖可以直接由圖形硬件直接操控,所以通過將視圖的內容緩存到位圖中, 可以獲得更好的性能。除了緩存視圖內容,使用Core Animation可以定義任意的可視對象,然后將該對象添加到視圖上,最后動畫該對象。
Core Animation讓視圖和可視對象的變化能以動畫的形式呈現。大部分變化都與可視對象屬性的更改相關。比如Core Animation能讓視圖的位置、尺寸或者透明度的變化以動畫的形式呈現。當你更改了這些屬性的值, Core Animation會在當前屬性值和最新指定的屬性值之間進行動畫。你一般不需要像卡通片那樣,完成每秒60次的視圖內容替換。相反,你應該利用Core Animation提供的移動視圖、淡入淡出視圖、對視圖應用任意的變換、改變視圖的其他可視屬性等方式完成動畫 。
圖層是繪圖與動畫的基礎
圖層對象是組織在三維空間的二維平面。它是使用Core Animation執行任何操作的核心構件。和視圖一樣,圖層的可管理信息包括幾何結構、內容、可視屬性;與視圖不同的是,圖層沒有定義它自己的外觀,圖層僅管理周圍位圖的狀態信息。位圖可以是視圖的繪圖結果或者一張圖片。因此主要的圖層被認為是模型對象,因為它們主要是用於管理數據。此概念務必記住,因為它影響到動畫的行為。
基於圖層的繪圖模型
大部分圖層不做實際的繪圖操作。相反,圖層捕獲應用的內容並緩存它們到位圖中。位圖有時也被稱為儲備(backing store)。當你隨后改變了一個圖層的屬性值,你做的所有工作只是改變了與圖層對象相關聯的狀態信息。當你的更改觸發了一個動畫,Core Animation會將該圖層對應的位圖數據和圖層的狀態信息發送給圖形處理硬件。圖形處理器所做的工作是根據獲得的信息對位圖進行渲染,如圖1-1所示,用圖形處理硬件操縱位圖要比圖形處理軟件能獲得更好的動畫效果。
因為操縱的是靜態的位圖,基於圖層的繪圖和基於視圖的繪圖在技術上有明顯的不同。對基於視圖的繪圖,對視圖的改變經常會觸發調用視圖的drawRect:方法以重繪視圖內容。但是此種方式的代價相對較高,因為它是CPU在主線程上的操作。Core Animation通過盡可能的使用圖形硬件操縱緩存后的位圖來避免了這種開銷,從而完成相同或相似的效果。
雖然Core Animation盡可能的使用緩存后的內容,但也必須提供初始內容並不時地進行內容更新。
基於圖層的動畫
圖層的位圖數據和狀態信息是從圖層內容的可視呈現中被分離了出來的。在從舊的屬性值動畫到新的屬性值的過程中,這種分離性給了Core Animation介入此過程的機會。例如改變一個圖層的position屬性會引起Core Animation將圖層從當前的位置移動到新的具體位置。對其他屬性做相似的改變將引起適當的動畫效果。圖1-2展示了一些可以執行在圖層上的動畫類型。
圖1-2 可在圖層上執行的動畫類型
在動畫運行期間,Core Animation使用硬件幫你完成繪畫每一幀的工作。你只需要指定動畫的開始點和結束點,剩下的工作就交給Core Animation就行了。如若需要,你也可以指定自定義時間信息(比如動畫運行多久)和動畫參數,如果你沒有指定, Core Animation會提供適當的默認值。
圖層對象定義了自己的幾何結構
圖層的其中一項任務就是管理自身內容的幾何結構。幾何結構包含關於圖層內容的邊界bound、在屏幕上的位置origin,是否圖層已被旋轉、縮放,或應用了某種變換。與視圖類似,一個圖層有一個frame和bound屬性,你可以使用這兩個屬性來定位圖層和它的內容。圖層也有一些視圖所沒有的屬性,比如anchor屬性,任何變換操作都是圍繞該點運轉的(可以理解為一個按在圖層上的圖釘)。圖層的某些幾何概念的指定方式與視圖信息的指定方式也有不同。
圖層使用兩種類型的坐標系統
圖層利用基於點的坐標系統和單位坐標系統指定內容的布局。坐標系統的選擇依賴於被傳達的信息類型。當指定的值是直接映射到屏幕或相對於其他圖層的坐標,比如圖層的position屬性,則使用基於點的坐標系統。當指定的值是相對於一些其他的值,與屏幕坐標不相關聯,則使用單位坐標。比如圖層的anchorPoint屬性,anchorPoint屬性指定了相對於圖層邊界的一個點。anchorPoint屬性可以更改。
基於點的坐標最常用於指定圖層的尺寸和位置。可通過圖層的bounds和postion屬性設定圖層的尺寸和位置。bound定義了圖層自身的坐標系統並包含圖層在屏幕上的尺寸。position屬性定義了圖層相對於父坐標系統的位置。雖然圖層有一個frame屬性,該屬性實際上是從bounds和position屬性派生而來,極少被使用。
圖層的bounds和frame矩形的方向始終與底層平台的默認方向相匹配。圖1-3顯示了iOS和OS X平台上邊界矩形的默認方向。在iOS中,默認情況下邊界矩形的原點在圖層的左上角。而OS X上邊界矩形的原點則在左下角。如果你在iOS和OS X版本的app之間共享Core Animation代碼,一定要對此種情況做區別對待。
圖1-3 iOS與OS X平台默認的圖層幾何結構
注意到圖1-3中position屬性被定位在圖層的中間位置。position屬性的變化參照圖層的anchorPoint屬性。
錨點是使用單位坐標系統的屬性之一。Core Animation中使用單位坐標表示的屬性值可能會因為圖層的尺寸變化而發生改變。你可以把單位坐標當做是總數的百分比。在單位坐標系統空間中每一個坐標的取值范圍在0.0和1.0之間。比如說,沿着x軸,最左側是0.0,最右側是1.0.沿着y軸,單位坐標值方向的不同依賴於具體的平台。如圖1-4所示。
圖1-4 iOS和OS X平台默認的單位坐標系統
注意:直到OS X 10.8才出現了geometryFlipped屬性,該屬性可以改變默認圖層y坐標的方向。當翻轉變換被調用時,使用該屬性來調整圖層的方向有的時候是必需的。如果父視圖使用了翻轉變換,它的子視圖內容(以及它對應的圖層)將經常被顛倒。在這種情況下,設置子圖層的geometryFlipped屬性為YES是一種修正該問題最簡單的方法。在OS X 10.8及以上版本,AppKit負責管理該屬性,你不應該更改它。對於iOS app,不推薦使用geometryFlipped屬性。
所有的坐標值,無論是點還是單位坐標都以浮點數指定。使用浮點數允許你更精確的指定可能落在整數坐標值之間的點。特別是打印或向retina屏幕繪圖,浮點值都會很方便。浮點值允許你忽略底層設備的分辨率,而你只需要指定值的精度。
錨點影響幾何結構的操作
圖層的幾何操是相對於圖層的錨點進行的,anchorPoint屬性可以訪問圖層的錨點值。當改變圖層的postion和transform屬性值,錨點的影響就很明顯。position屬性是相對於圖層的錨點被指定。並且任何你對圖層陰影的變換操作也是相對於錨點。
圖1-5說明了改變錨點對圖層position屬性的影響。盡管圖層沒有在它的父邊界內移動,將錨點從中心點移動到圖層邊界的原點將改變position屬性值。
圖1-5 錨點是如何影響圖層的position屬性
圖1-6顯示了錨點的變化是如何影響到應用在圖層上的變換。當你對圖層應用了一個旋轉變換,圖層將圍繞中心點旋轉。因為默認情況下,錨點被設定在圖層的中心位置。一般創建這類旋轉行為正是你所期望的。然而如果你改變了錨點值,旋轉的結果也會發生變化。
圖1-6 錨點是如何影響圖層的變換
圖層可在三維空間中操作
圖層有兩個操控圖層和內容的變換矩陣, transform和sublayerTransform屬性。 CALayer的transform屬性用來指定應用到圖層和它內嵌的子層上的變換。通常當你想更改圖層本身則使用該屬性。比方說,你可能使用該屬性縮放或旋轉圖層,或是臨時的改變圖層的位置。sublayerTransform屬性定義了僅應用在子層上的變換以及給場景內容添加透視效果。變換行為由多個坐標確定,這些坐標通過一個數字矩陣來獲取,獲取的坐標結果是原始點被變換后的版本。因為Core Animation的值可在三維空間中指定,每個坐標點有四個值,這四個值需要和一個4*4的矩陣相乘。如圖1-7所示。在Core Animation中,圖中的變換由CATransform3D類型表示。幸運的是,你不需要直接更改這個結構中的域值來執行標准變換。Core Animation為創建縮放、平移、旋轉矩陣以及矩陣比較提供了綜合的函數集。除了使用函數操縱變換。Core Animation擴展了鍵值編碼以支持你使用鍵路徑更改一個變換操作。
圖1-7 使用矩陣數學轉換一個坐標
圖1-8顯示了對一些常見的變換矩的配置。任何坐標與單位矩陣相乘將返回完全相同的坐標。坐標如何被更改完全依賴於你變化的矩陣因子。比如,為了僅在X軸上平移,你只需對變換矩陣的tx因子應用一個非零值,並讓ty和tz值為0。如果是旋轉,應用一個旋轉角度的正弦和余弦值。
圖1-8 常見變換的矩陣配置
不同的圖層樹反映了不同的動畫狀態
使用Core Animation的app擁有三個圖層對象集合。每一個圖層對象集合在呈現app內容上都扮演着不同的角色。
Ø
模型圖層樹中的對象(或簡稱“圖層樹”)用的最多。在這個樹中的對象是模型對象,模型對象負責存儲所有動畫的目標值。無論何時改變圖層的屬性值,你使用的始終是某一個模型對象。
Ø
呈現樹中的對象包含所有運行中的動畫的瞬時值。圖層樹對象包含的是動畫的目標值,而呈現樹中的對象代表顯示在屏幕上動畫的當前值。你不應該更改這個樹中的對象。相反,你使用這些對象來讀取當前動畫的值,可能用於創建開始於這些值的新的動畫。
Ø 在渲染樹中的對象執行實際的動畫,並且對Core Animation是不公開的。
每一個圖層對象集合被組織在一個層次結構中,類似於app中的視圖。事實上,在一個所有視圖都支持圖層功能的app來說,每一個樹的初始結構完全與圖層的層次相匹配。一個app可以添加另外的圖層對象,因此圖層與視圖是不相關聯的。可以按照需求將圖層對象插入到指定的視圖層級中。你可能為了優化app內容的性能而選擇加入圖層對象而非視圖,原因在於圖層的開銷要比視圖低。圖1-9展示了出現在一個簡單iOS app的圖層分解圖。在示例中窗口包含了一個內容視圖,內容視圖包含了一個按鈕視圖和兩個獨立的圖層對象。每一個視圖擁有一個相對應的構成圖層層次的圖層對象。
圖1-9 與窗口相關聯的圖層
在圖層樹中的每一個對象,在渲染樹和呈現樹中也存在一個與之匹配的對象。如圖1-10所示。正如之前提到過的,app主要與圖層樹中的對象進行交互,但可能有時會訪問呈現樹中的對象。具體地,訪問圖層樹中對象的presentationLayer屬性將返回一個在呈現樹中相對應的對象。你可能會通過該對象獲取在動畫執行過程中的某一時刻的屬性值。
圖1-10 窗口中的圖層樹
重要:只在動畫運行時訪問呈現樹中的對象。當動畫在進行中,呈現樹就包含了圖層顯示在屏幕上的那一刻的值。該行為與圖層樹不同,圖層樹永遠只表示最終的目標值。
圖層不是視圖的替代品。因此無法創建一個基於單一圖層對象的可視界面。圖層是視圖的基礎設施。具體地,圖層讓視圖的繪圖和動畫更簡單和高效,並且能在繪圖和動畫時保持高幀率。然而許多事情圖層無法做到。圖層不能處理事件、繪制內容,特別是在響應鏈中,或是做一些其他的事情。因此,每個app必須有一個或多個視圖來處理這類交互。
在iOS中,每一個視圖有一個相對應的圖層對象。但在OS X中,你必須決定哪一個視圖擁有圖層。OS X 10.8以及之前的版本中,添加圖層到所有視圖上也許是有意義的。然而你不必這樣做,你仍可以關閉圖層防止不必要以及無根據的開銷。
圖層在某種程度上減少了程序的內存開銷,圖層的這些優點遠遠超出它的缺點。所以最好是在禁用圖層支持之前測試你app的性能。
當你的視圖支持圖層后,此時該視圖就屬於layer-backed視圖。對於layer-backed視圖,系統會負責為其創建底層的圖層對象,並保持與視圖的同步。所以iOS視圖都是支持圖層的並且在OS X中的大部分視圖也是如此。但是在OS X中,你也可以創建圖層托管(layer-hosting view)視圖,該視圖的圖層對象由你來提供。
注意:對於layer-backed視圖,建議只操作視圖而不是圖層。iOS中視圖僅僅是對圖層對象的精簡包裝,所以任何你對圖層的操作通常都會正常工作。但是iOS和OS X平台上操縱圖層而不是圖層可能不會取得預期的結果。該文檔會盡可能的指出這些陷阱並提供一種方式讓你可以與它們工作。
除了與視圖相關的圖層,你也可以創建獨立的圖層對象。你可以嵌入這些獨立圖層對象到任何其他圖層對象中,包括與視圖相關的圖層。你一般使用圖層對象作為具體優化方法的一部分。比如說,如果你想使用相同的圖片在多個地方,你可以只加載圖片一次,然后將圖片和多個獨立的圖層對象相關聯,最后添加這些圖層對象到圖層樹中。每一個圖層只會引用源圖片而不是嘗試在內存中創建新的圖片拷貝。
構建圖層對象
圖層是Core Animation的核心。圖層管理着應用程序的可視內容,圖層提供了更改內容樣式與可視外觀的選項。iOS是自動支持圖層的,而如果你是一名OS X開發者,那你必須手動啟用圖層支持。一旦開啟了圖層支持,你必須理解如何配置和操控圖層以獲得你想要的效果。
開啟對Core Animation的支持
在iOS app中,Core Animation總是開啟的,視圖也都是支持圖層的。而在OS X中,app必須顯示地通過以下方式開啟對Core Animation的支持:
Ø 鏈接QuartzCore框架(如果iOS app顯式地使用了Core Animation則必須鏈接此框架)。
Ø 你可選擇以下任意一種方式為NSView開啟圖層支持:
u 在你的nib文件中,使用View Effect檢視面板讓視圖支持圖層。檢視面板會對選中的視圖和它的子視圖顯示一個復選框。推薦做法是盡可能的在窗口的內容視圖中開啟圖層支持。
u 對於使用代碼創建的視圖,調用視圖的setWantsLayer:方法,並傳入一個YES值以表明當前的視圖是支持圖層的。
使用上面所提到的方式開啟圖層支持可創建一個支持圖層的視圖。系統負責創建視圖底層的圖層對象,並保持對圖層的更新。在OS X中,也可以創建圖層托管視圖,該視圖底層的圖層由你的app創建和管理(你不能在iOS中創建圖層托管視圖)。
改變與視圖相關聯的圖層對象
支持圖層的視圖默認會創建一個CALayer類型的圖層實例,一般情況下你可能不需要選擇其他類型的圖層實例。但是Core Animation根據不同的應用場景提供了一些不同類型的圖層類,每個圖層類型都擁有特殊的功能。選擇適當的圖層類可能會提升app的性能或者能以簡單的方式支持指定的內容類型。比如CATiledLayer類在顯示大圖片上將更加高效。
改變視圖自有的圖層類
你可以通過覆蓋iOS視圖中的layerClass方法並返回一個需要的圖層類對象。大部分的iOS視圖通過創建一個CALayer對象,使用該對象儲備視圖的內容。使用默認的圖層類型是個不錯的選擇,但在某些情況下,你可能需要使用特定特性的圖層對象。比如在下述情況下你需要改變圖層的類型:
Ø 視圖的繪圖內容是由OpenGL ES實現,此種情況你需要使用CAEAGLLayer對象。
Ø 特殊的圖層讓你擁有更強的表現性能。
Ø 需要利用某些特殊的Core Animation類,比如粒子發射器或者拷貝器。
改變一個視圖的圖層類型非常的簡單;所有你需要做得只是覆蓋layerClass方法並返回一個你想要替代的類對象。如清單2-1所示,視圖調用layerClass方法並使用返回的類為其創建新的圖層對象。一旦創建完成,視圖的圖層對象將不可改變。
清單2-1 一個iOS視圖圖層類的指定
+(Class) layerClass {
return [CAEAGLLayer class];
}
不同的圖層擁有特定的行為
Core Animation定義了許多標准的圖層類,每一個圖層類都有着各自的應用場景。CALayer類是所有圖層對象的根類,它定義了所有圖層對象必須支持的行為,它也是支持圖層的視圖的默認圖層類型。你可以指定表2-1中的某一個圖層類以改變默認的圖層類型。
類別 |
用途 |
CAEmitterLayer |
用於實現基於Core Animation粒子發射系統。發射器層對象控制粒子的生成和起源 |
CAGradientLayer |
用於繪制一個顏色漸變填充圖層的形狀(所有圓角矩形邊界內的部分) |
CAEAGLLayer/CAOpenGLLayer |
用於設置需要使用OpenGL ES(iOS)或OpenGL(OS X)繪制的內容與內容儲備。 |
CAReplicatorLayer |
當你想自動生成一個或多個子層的拷貝。復制器為你生成拷貝並使用你指定的屬性值以修改復制品的外觀和屬性。 |
CAScrollLayer |
用於管理由多個子區域組成的大的可滾動區域 |
CAShaperLayer |
用於繪制三次貝塞爾曲線。CAShaperLayer對繪制基於路徑的形狀非常有幫助。因為CAShaperLayer總是生成一個最新的路徑。而如果將路徑畫在圖層儲備中,一旦圖層被縮放,形狀就變形了。 |
CATextLayer |
用於渲染一個無格式或屬性文本字符 |
CATransformLayer |
用於渲染一個真3D的圖層層級。而不是由其他圖層類實現的2D圖層層級。 |
QCCompositionLayer |
用於渲染一個Quartz組件元素(僅在OS X中有效) |
提供圖層的內容
圖層是管理app內容的數據對象。圖層的內容由包含可視數據的位圖構成。使用下述三種方式之一可給提供圖層的內容:
l 直接賦值一個UIImage對象給圖層對象contents屬性。(這個技術適用於圖層內容從不或幾乎不改變的情形。)
l 賦值一個代理給圖層,由代理負責繪制圖層內容。(該技術適用於圖層內容可能偶爾改變,且內容可由外部對象提供,比如視圖。)
l 定義一個CALayer的子類並覆蓋類的繪圖方法,有覆蓋的方法返回圖層的內容。(該技術適用於你需要創建自定義圖層的子類,或者你想改變圖層基本的繪圖行為。)
你需要為提供圖層內容而擔心的時刻僅在手動創建圖層對象。如果你的app中只包含支持圖層的視圖。那你不需要擔心使用剛剛提到的這些提供圖層的方法提供圖層內容。支持圖層的視圖會使用盡可能高效的方式為與之相關的圖層提供內容。
使用圖片為圖層提供內容
因為一個圖層僅是管理位圖圖片的容器,所以你可以直接賦值一個圖片給圖層的contents屬性。賦值一個圖片給圖層很簡單,只需要指定一張你想顯示在屏幕上的具體的圖片就可以了。圖層將直接使用你提供的圖片對象,並不會嘗試創建自己的圖片拷貝。當你的應用使用相同的圖片在多個地方時,該行為可以節省許多內存。
你賦值的圖片類型必須是CGImageRef類型(在OS X 10.6或之前版本,你也可以賦值一個NSImage對象。)當賦值圖片時,記住提供的圖片的分辨率要與本地設備的分辨率相匹配。對於Retina顯示設備,這可能也需要你去調整圖片的contentsScale屬性。
使用代理提供圖層的內容
如果圖層的內容是動態改變的,你可以使用一個代理對象在需要的時候提供圖層並更新內容。圖層顯示的時候,圖層調用你的代理方法以提供需要的內容:
Ø 如果你的代理實現了displayLayer:方法,實現方法負責創建位圖並賦值給contents屬性。
Ø 如果你的代理實現的是drawLayer:inContext:方法,Core Animation創建一個位圖,創建一個用於繪制位圖的上下文,並調用代理方法填充該位圖。你的代理方法所要做的是將內容畫在圖形上下文上。
代理對象必須實現displayLayer:或者drawLayer:inContext方法之一。如果代理對象把這兩個方法都實現了,圖層只調用displayLayer:方法。
覆蓋displayLayer:方法在當你的app更傾向於載入或創建想要顯示的位圖的情況下適用。清單2-3顯示了一個實現displayLayer:代理方法的示例代碼。在這個例子中,代理使用了一個輔助對象來加載和顯示它需要的圖片。代理方法根據內部的狀態選擇哪張圖片用於顯示。例子中得displayYesImage是一個自定義屬性。
清單2-3:直接設置圖層的內容
- (void) displayLayer:(CALayer *)theLayer {
//檢查某些屬性狀態值
if (self.displayYesImage) {
// 顯示Yes圖片
theLayer.contents = [someHelperObject loadStateYesImage];
} else {
// 顯示No圖片
theLayer.contents = [someHelperObject loadStateNoImage];
}
}
如果你沒有預渲染的圖片或者輔助對象來創建位圖。代理對象可以使用drawLayer:inContext:方法動態的繪制內容。清單2-4顯示了一個對drawLayer:inContext方法實現的例子。在該例子中,代理對象使用了固定的寬度和當前的渲染顏色繪制了一個簡單的曲線路徑。
清單2-4:繪制圖層上下文
- (void)drawLayer:(CALayer *)theLayer inContext:(CGContextRef)theContext {
CGMutablePathRef thePath = CGPathCreateMutable();
CGPathMoveToPoint(thePath,NULL,15.0f,15.f);
CGPathAddCurveToPoint(thePath,
NULL,
15.f,250.0f,
295.0f,250.0f,
295.0f,15.0f);
CGContextBeginPath(theContext);
CGContextAddPath(theContext, thePath);
CGContextSetLineWidth(theContext, 5);
CGContextStrokePath(theContext);
// Release the path
CFRelease(thePath);
}
帶有自定義內容並支持圖層的視圖,你應該去覆蓋視圖的繪圖方法。一個支持圖層的視圖自動創建它自己的圖層代理並實現需要的代理方法,你不應該改變這個配置。相反,你應該實現你視圖的drawRect:方法以繪制你的內容。
在OS X 10.8和之前的版本中,繪圖的另外方法是通過覆蓋你視圖的wantsUpdateLayer和updateLayer方法提供一個位圖。覆蓋wantsUpdateLayer並返回YES會引起NSView類調用替換的渲染路徑。相對於調用drawRect:方法,視圖調用你的updateLayer:方法,方法的實現必須直接賦值一個位圖給圖層的contents屬性。這個AppKit期望你直接設置視圖的圖層對象內容的一種方案。
由子類提供圖層的內容
如果你實現了一個自定義的圖層類,你可以覆蓋圖層類的繪圖方法完成任何繪圖的操作。用這種方法生成圖層對象的自定義內容是罕見的,但是某些圖層卻擁有管理顯示的內容能力。如CATiledLayer類通過將大圖片拆成更小的可管理、可獨立渲染的碎片來管理大的圖片。因為只有圖層知道在某一時刻哪一個碎片需要被渲染,圖層會直接管理繪圖的行為。
當子類化圖層類,你可使用下述的兩種方式繪制你的圖層內容:
l 覆蓋圖層的display方法並使用在方法中直接設置圖層的contents屬性。
l 覆蓋圖層的drawInContext:方法並將需要的內容繪制到提供的圖形上下文中。
選擇何種方法依賴於在繪圖過程中你需要多少的控制。display方法是更新圖層內容的主要入口點,所以覆蓋這個方法讓你處於完全的過程控制中。覆蓋display方法也意味你需要負責contents屬性創建CGImageRef對象。如果你只是想繪制內容(或讓你的圖層管理繪圖操作),你可以覆蓋drawInContext:方法並讓圖層為你創建內容儲備。
調整提供的內容
當給圖層的contents屬性賦值一個圖片,圖層的contentsGravity屬性確定圖片如何適合當前的邊界。默認情況下,如果一個圖片大於小於當前的邊界,圖層對象縮放圖片以適應有效的空間。如果圖層的長寬比和圖片的長寬比不一致,這會導致圖片被扭曲。所以你可以使用contentsGravity屬性來確定你的內容以最佳的方式被呈現。
你可以向contentsGravity屬性賦予的值分為兩個分類:
l 基於位置的引力約束允許你固定你的圖片到圖層矩形邊界的一個特殊的邊緣或角落,不會縮放圖片。
l 基於縮放的引力約束允許你伸縮圖片使用多個選項之一,某些選項保留長寬比,有些則不保留。
圖2-1 顯示了基於位置的引力設置如何影響你的圖片。除了kCAGravityCenter約束,每一個約束都將圖片固定在圖層矩形邊界的某個邊緣或角落。kCAGravityCenter約束將圖片居中在圖層中。基於位置的引力約束沒有一個選項會縮放圖片。所以圖片總是和它原始的尺寸一樣的情況下被渲染。如果你的圖片大於圖層的邊界,這可能導致部分圖片被裁減;如果圖片小於矩形邊界,如果設置了背景顏色的話,則圖片沒有覆蓋到得區域則顯示圖層的背景顏色。
圖2-1 基於位置的圖層引力約束
圖2-2顯示了基於縮放的引力約束如何影響你的圖片。如果圖片不是准確匹配圖層矩形邊界范圍內,所有這些約束都會縮放你的圖片。這些模式選項之間所不同的是如何處理圖片原始的長寬比。一些模式保留原始圖片的長寬比,有些則不保留。默認情況是,圖層的contentsGravity屬性被設置為kCAGravityResize常量值,它是唯一一個不保留圖片的長寬比的選項。
圖2-2 基於縮放的圖層引力約束
使用高分辨率的圖片
圖層並不知道當前設備的分辨率信息。圖層只是簡單的存儲一個指向位圖的指針,並用給定的有效像素以最佳的方式顯示。如果你賦值一個圖片給圖層的contents屬性,你必須給圖層的contentsScale屬性設置一個正確的值以告訴Core Animation關於圖片的分辨率。默認的屬性值為1.0,對於在標准分辨率的屏幕上顯示圖片是正確的。如果你的圖片要在Retina屏幕上顯示,該值需要設定為2.0。使用[[UIScreen mainScreen] scale]可獲取正確的縮放率。
僅在直接賦值一副位圖給圖層才需要改變contentScale屬性的值。在UIKit或AppKit中,一個支持圖層的視圖會被自動設置基於屏幕分辨率的圖層縮放因子,並且圖層內容由視圖管理。
在OS X中,基於位置的引力約束影響圖片呈現的方式。從NSImage對象選擇賦值給圖層。因為這些實例不會引起圖片的縮放。Core Animation依靠contentsScale屬性以最佳的像素密度呈現圖片。
在OS X中,圖層的代理可以實現layer:shouldInheritContentScale:fromWindow:方法,使用該方法響應對縮放因子的改變。可能因為窗口會在標准分辨率和高分辨率屏幕之間移動,AppKit會在窗口分辨率變化時自動調用代理的實現方法。如果代理支持圖層的圖片分辨率的變化,則此方法返回YES。當需要反映分辨率的變化,該方法會更新圖層的內容。
調整圖層的可視樣式和外觀
圖層對象擁有內建的可視裝飾,如邊框、背景色。你可以使用這些裝飾對圖層的主內容進行補充。因為這些可視的裝飾不需要任何渲染。裝飾讓圖層在一些情況下讓圖層成為獨立的實體成為可能。你只需設置圖層的屬性,圖層自會處理必要的繪圖工作,包括動畫。
圖層擁有自己的背景和邊框
圖層除了可以顯示基於圖片的內容,還可以顯示被填充的背景、描邊的邊框。背景被渲染在圖層的內容圖片的后方,邊框被渲染在內容圖片的前方。如圖2-3顯示。如果圖層包含子層,子層也會顯示在邊框的下方。因為背景顏色是處在圖片的后方的,背景會從圖片的任何透明的地方透射出來。
圖2-3 給圖層添加邊框和背景色
清單2-5顯示了設置圖層的邊框和背景顏色代碼。所有的這些屬性都是可動畫的。
清單2-5:設置圖層的背景色和邊框
myLayer.backgroundColor = [NSColor greenColor].CGColor;
myLayer.borderColor = [NSColor blackColor].CGColor;
myLayer.borderWidth = 3.0;
注意:你可以將圖層的背景設置成任何顏色,包括支持透明的顏色或使用模式(pattern)圖片。使用模式圖片時,渲染是由Core Graphics完成,它使用的是標准坐標系統。標准坐標系統和iOS的坐標系統是不一樣的。所以,圖片在iOS上得渲染結果是顛倒的。可以使用[backgroundLayer setTransform:CATransform3DMakeScale(1.0, -1.0, 1.0)];解決該問題。
如果你將圖層的背景顏色設置為不透明。考慮將圖層的opaque屬性設置為YES。當合成屏幕上的圖層時可以提升系能,圖層的后備存儲也不需管理alpha通道。如果你給圖層設置了非零的圓角半徑,則不可以將圖層標記為不透明類型。
圖層支持圓角半徑
你可以通過給圖層添加一個圓角半徑來創建一個圓角矩形。圓角半徑是一個可視的裝飾,它遮罩了圖層邊界矩形的部分區域以允許底層內容的顯示。如圖2-4所示,因為它包括了應用一個透明蒙版,圓角半徑不影響圖層的contents屬性中得圖片,除非masksToBounds屬性被設置為YES。然而,圓角半徑總是影響圖層的背景顏色和邊框的繪圖方式。
圖2-4 圖層的圓角半徑
為了設置圓角半徑,只需要為圖層的cornerRadius屬性設置一個值。你指定的半徑值是以點衡量的,並且圓角半徑會應用到圖層的四個角上。
圖層內建支持陰影
CALayer類包括若干個配置陰影的屬性。陰影讓圖層更加有深度,好像圖層浮起來了。陰影也是另一種裝飾。在具體的環境你會發現陰影非常的有用。使用圖層你可以控制陰影的顏色,相對於圖層內容的位置,透明度以及形狀。
圖層陰影的陰影透明度shadowOpacity默認被設置為0,這有效地隱藏了陰影。改變透明度為非0值將引起Core Animaiton繪制陰影。因為默認情況下,陰影被直接定位在圖層的下方。所以你可能為了能夠看到陰影而改變陰影的偏移量。你指定的偏移量被應用到圖層上使用的是圖層的本地坐標系。本地坐標系在iOS和OS X上的表現是不同的。如圖2-5所示,圖中顯示了一個帶有偏下偏右陰影的圖層。在iOS中,這需要指定一個正值,而在OS X中需要指定一個負值。
圖2-5 給圖層設置陰影
給一個圖層的contents屬性賦值一張只有中間部分存在圖像四周為透明的圖片,並對圖層施加陰影,陰影的偏移量為(5,5)。則該陰影只會出現在紅色圓的偏右下出現。如果給圖層的backgroundColor屬性賦值,則此時的陰影就會出現在圖層邊界的偏右偏下方。圖層產生陰影的條件包括設置圖層的shadowOffset屬性值、shadowOpacity屬性值、圖層或其子層的contents屬性值或者backgroundColor屬性值。
圖2-6 四周透明的圖片
當給圖層添加陰影,陰影就是圖層內容的一部分,但實際上陰影擴展到了圖層邊界矩形的外圍。結果是,如果你啟用了圖層的masksToBounds屬性,圍繞邊緣的陰影將被裁減掉。如果你的圖層中含有任何透明的內容,這將引起一個古怪的效果,圖層下方的陰影部分依然可以看見,但是超出圖層邊界的部分就沒有了。如果既想要陰影又要啟用maskToBounds為YES,那么你可以使用兩個圖層。第一個是包含內容的圖層,將該圖層的maskToBounds屬性設置為YES,然后創建一個相同尺寸且含有陰影效果的第二個圖層,最后調用第二個圖層的addSublayer方法將第一個圖層嵌入到第二個圖層中就可以了。
注意:iOS平台上不能給圖層添加濾鏡
給圖層添加自定義屬性
CAAnimation和CALayer類擴展了鍵值編碼以對自定義屬性進行支持。你可以使用該行為給圖層添加數據並使用你定義的鍵檢索對應的值。你甚至可以給你的自定義屬性關聯動作,當該屬性的值發生變化,對應的動畫將會被執行。
打印圖層支持的視圖內容
在打印過程中,當需要適應打印環境時,圖層將重繪它們的內容。盡管當向屏幕上做渲染的時Core Animation一般是依賴緩存位圖。但在打印時它將重繪這些內容。特別情況下,如果支持圖層的視圖使用drawRect:方法提供圖層內容,Core Animation將再次調用drawRect:方法以生成可打印的圖層內容。
圖層的內容動畫
Core Animation提供的基礎設施讓輕松創建復雜圖層動畫變得異常簡單,Core Animation擴展了所有擁有圖層的視圖。例如改變圖層框架矩形的尺寸,改變其在屏幕上的位置,應用旋轉變換,改變它的透明度。使用Core Animation初始化一個動畫和改變屬性一樣簡單,但你也可以顯式的創建一個動畫並設置動畫的參數。
用簡單的動畫表現圖層屬性的變化
你可以以顯式或隱式的執行簡單的動畫。隱式動畫使用默認的定時器和動畫屬性展現動畫。而顯式動畫需要你為動畫對象配置一些參數。所以當默認的定時器能夠很好的為你服務並且你所要的動畫效果不需要太多代碼時,隱式動畫則非常的適合你。
簡單的動畫包括改變一個圖層的屬性,以及隨着時間的推移,讓Core Animation以動畫的形式展現這些屬性的變化。圖層定義了許多會影響圖層可視外觀的屬性。改變這些屬性是以動畫方式展現外觀變化的一種方式。例如將圖層的透明度從1.0修改為0.0,這將引起圖層的淡出特效,最后圖層變為透明。
重要:雖然你可以使用Core Animation接口直接讓支持圖層的視圖產生動畫,但這樣做經常需要額外的步驟。
為了觸發隱式動畫,你所要做的是更新圖層對象的屬性。當更改的目標是圖層樹中的圖層對象,更改將立即反映到對象上。而圖層對象的可視外觀並不會立即發生變化,Core Animation將圖層對象屬性的變化當做是一個觸發器,用以創建和安排一個或多個可執行的隱式動畫。如清單3-1那樣修改圖層對象屬性,將引起Core Animation為你創建動畫對象,動畫對象將被安排在下一次更新周期運行。
清單3-1 隱式動畫的方式呈現圖層屬性的變化
theLayer.opacity = 0.0;
為了顯式地使用動畫對象呈現相同的變化,創建一個CABasicAnimation對象並配置該對象的動畫參數。在添加動畫到圖層之前,你可以設置動畫的開始值與結束值,改變持續時間,或任何其他動畫參數。你指定你想要動畫的屬性的鍵路徑,接着設置動畫參數。為了執行一個動畫,你使用addAnimation:forKey:方法將動畫對象添加到你想要展現動畫的圖層上。
清單3-2 顯式動畫的方式呈現圖層屬性的變化
CABasicAnimation* fadeAnimation = [CABasicAnimation animationWithKeyPath:@”opacity”;
fadeAnimation.fromValue = [NSNumber numberWithFloat:1.0];
fadeAnimation.toValue = [NSNumber numberWithFloat:0.0];
fadeAnimation.duration = 1.0;
[theLayer addAnimation:fadeAnimation forKey:nil];
//改變圖層實際的最后數據值
theLayer.opacity = 0.0; // 記得更新圖層樹
注意:創建一個顯示動畫,推薦是賦值一個值給動畫對象的fromValue屬性。如果你沒有為該屬性指定值,Core Animation將使用圖層的當前值作為開始值。如果已經更新了屬性作為它的最終值,這將致使fromValue屬性值遭到干擾,結果可能並不是你想要的。
不同於隱式動畫,隱式動畫會更新圖層對象的值。而顯示動畫不會更改圖層樹中的數據。顯示動畫僅是創建了一個動畫。在動畫結束之后,Core Animation從圖層中移除該動畫對象並使用當前的數據值重繪圖層。如果你想讓顯示動畫的改變成為永久性的,如你在之前的例子中看到的,你必須更新圖層屬性。
隱式和顯示動畫都會在當前運行循環周期結束之后開始執行,並且當前的線程必須擁有一個用於執行動畫的運行循環runloop。如果你改變了圖層的動畫屬性或者給圖層添加了多個動畫對象。所有這些動畫將會同時被執行。例如將圖層移動到屏幕之外的時候添加漸隱動畫,這兩個動畫是同時運行的。然而你可以讓動畫對象在一個特殊的時間開始執行。
用關鍵幀動畫表現一個圖層屬性的變化
盡管一個基於屬性的動畫是從開始值到結束值改變一個屬性,一個CAKeyfarmeAnimation對象讓你通過一個目標集合產生動畫,在某種程度上,動畫可能是線性的或者非線性。一個關鍵幀動畫由一個目標數據值集合組成。每個值的時間應該是可達的。最簡單的配置是你使用一個數組指定值和時間。對於一個圖層位置的變化,你可以有一個跟隨路徑的變化。動畫對象使用你指定的關鍵幀並通過在某個值到另一個在給定時間區間內插值的方式構建動畫。
圖3-1顯示了一個圖層position屬性的5秒動畫。position是動畫跟隨一個路徑。使用一個CGPathRef數據類型指定的。這個動畫的代碼如清單3-3所示。
圖3-1 一個圖層的postion屬性的5秒關鍵幀動畫
代碼清單3-3 創建一個跳彈關鍵幀動畫
// 創建一個實現兩個弧線的CGPath(一個跳彈路徑)
CGMutablePathRef thePath = CGPathCreateMutable();
CGPathMoveToPoint(thePath,NULL,74.0,74.0);
CGPathAddCurveToPoint(thePath,NULL,74.0,500.0,
320.0,500.0,
320.0,74.0);
CGPathAddCurveToPoint(thePath,NULL,320.0,500.0,
566.0,500.0,
566.0,74.0);
CAKeyframeAnimation * theAnimation;
//創建一個動畫對象,指定位置屬性作為鍵路徑
theAnimation=[CAKeyframeAnimation animationWithKeyPath:@"position"];
theAnimation.path=thePath;
theAnimation.duration=5.0;
CGPathRelease(thePath);
// 為圖層添加動畫
[theLayer addAnimation:theAnimation forKey:nil];
指定關鍵幀的值
關鍵幀的值是關鍵幀動畫中最重要的部分。這些值定義了動畫在整個執行期間內的行為。指定關鍵幀值的主要方式以對象數組作為它的值。但是對於包含CGPoint數據類型(比如圖層的anchorPoint屬性和position屬性),你可以指定一個CGPathRef數據類型替代。
當指定一個關鍵幀值數組,你放到數組中內容依賴於屬性需要的數據類型。你可以直接添加一些對象到數組中。然而一些對象必須在添加到數組中之前被轉換為id類型,所有標量類型或結構體必須被包裝為對象,比如:
l 對於屬性類型為CGRect(例如bounds和frame屬性),使用NSValue對象包裝每一個矩形。
l 對於圖層的變換屬性,使用NSValue包裝每一個CATransform3D矩陣。動畫這個屬性將引起關鍵幀動畫給圖層輪流應用每個變換矩陣。
l 對於borderColor屬性,在添加到數組之前,轉換CGColorRef數據類型為id類型。
l 對於屬性為CGFlot類型,在添加到數組之前,使用NSNumber包裝每個值。
l 為了動畫圖層的內容屬性,指定一個CGImageRef數據類型屬性。
對於一個CGPoint數據類型的屬性,你可以創建一個點(使用NSValue對象包裝)數組,或者使用CGPathRef對象指定跟蹤的路徑。當你指定一個點數組,關鍵幀動畫對象在每一個連續的點之間繪制一條線,並沿着這些線移動。當你指定一個CGPathRef對象,動畫起始於路徑的開始點並跟隨路徑線移動,這包括沿着任何曲面。你可以使用開放的或者封閉的路徑。
指定關鍵幀動畫的定時器
關鍵幀動畫的定時與步調比基本動畫來的要復雜。以下是幾個用於控制定時和步調的屬性:
l calculationMode屬性定義了計算動畫定時的算法。該屬性值會影響其他與定時相關屬性的使用方式。
l 線性和曲線動畫,動畫的calculationMode屬性被設置為kCAAnimationLinear或CAAnimationCubic,屬性值被用於提供定時器信息以生成動畫。這些模式值讓你最大化控制動畫的定時器。
l 節奏動畫,動畫的calculationMode屬性被設置為kCAAnimationPaced或kCAAnimationCubicPaced,這些屬性值不依賴由keyTimes或timingFunctions屬性提供的額外定時器值。相反,定時器值被隱式地計算以提供一個常速率動畫。
l 離散動畫,動畫的calculationMode屬性被設置為kCAAnimationDiscrete,該值將引起動畫屬性從一個關鍵幀跳到另一個沒有任何補間動畫的下一個關鍵幀。計算模式使用keyTimes屬性值,但忽略timingFunctions屬性。
l keyTimes屬性為應用在每一關鍵幀指定應用到每一個關鍵幀上的計時器。該屬性只在calculationMode屬性被設置為kCAAnimationLinear,kCAAnimaitonDiscrete,kCAAnimationCubic時被使用。它不使用在節奏動畫中。
l timingFunctions屬性指定使用在每一個關鍵幀部分的定時曲線(該屬性替換了繼承的timingFunction屬性)。
如果你想自己處理動畫的定時,可以使用kCAAnimationLinear或kCAAnimaitonCubic模式與keyTimes和timingFunctions屬性。keyTimes定義了應用在每一關鍵幀的時間點。所有中間值的定時由定時函數控制,定時函數允許你對各個部分應用緩入或緩出曲線定時。如果你不指定任何定時函數,動畫將會是線性的。
停止一個隱式動畫的運行
動畫通常直到運行結束才會停止,但是你也可以根據需要使用以下技術提前停止動畫:
Ø 為了從圖層上移除單獨的動畫對象,調用圖層的removeAnimationForKey:方法移除你的動畫對象。該方法使用的鍵要與調用addAnimation:forKey:方法傳入的鍵一致。你指定的鍵必須不為nil。
Ø 為了移除圖層的所有動畫對象。調用圖層的removeAllAnimations方法。該方法立即會移除所有進行中的動畫,並使用圖層當前的狀態信息重繪圖層。
注意:你不能直接移除圖層的隱式動畫。
當你從圖層上移除一個動畫,Core Animation通過使用圖層當前的值重繪圖層做出響應。因為當前的值通常是動畫的結束值,這可能會引起圖層的外觀突然的跳躍。如果你想圖層的外觀仍然在動畫最后一幀出現的地方。你可以檢索在呈現樹中的對象的最終值,並設置這些值到圖層樹中的對象。
關於臨時暫停動畫的信息,請參閱“清單5-4”。
同時動畫多個屬性變化
如果你想同時給一個圖層對象應用多個動畫,你可以使用CAAnimationGroup對象將這些動畫放在一個組里。通過使用單獨的配置點,使用組對象簡化了對多個動畫對象的管理。應用於動畫組的定時器和持續值將使用相同的值覆蓋單個動畫對象。
清單3-4顯示了如何使用相同定時和持續時間的動畫組執行兩個與邊框相關的動畫。
清單3-4 同時執行兩個動畫
// Animation 1
CAKeyframeAnimation* widthAnim = [CAKeyframeAnimation animationWithKeyPath:@"borderWidth"];
NSArray* widthValues = [NSArray arrayWithObjects:@1.0, @10.0, @5.0, @30.0, @0.5,@15.0, @2.0, @50.0, @0.0, nil];
widthAnim.values = widthValues;
widthAnim.calculationMode = kCAAnimationPaced;
// Animation 2
CAKeyframeAnimation* colorAnim = [CAKeyframeAnimation animationWithKeyPath:@"borderColor"];
NSArray*colorValues=[NSArray arrayWithObjects:(id)[UIColorgreenColor].CGColor,(id)[UIColorredColor].CGColor,(id)[UIColorblueColor].CGColor, nil];
colorAnim.values = colorValues;
colorAnim.calculationMode = kCAAnimationPaced
// Animation group
CAAnimationGroup* group = [CAAnimationGroup animation];
group.animations = [NSArray arrayWithObjects:colorAnim, widthAnim, nil];
group.duration = 5.0;
[myLayer addAnimation:group forKey:@"BorderChanges"];
對於分組動畫的更高級的方式是使用一個事務對象。事務提供了更加靈活的方式,允許你創建內嵌的動畫集合以及給每一個動畫賦值不同的動畫參數。
檢測一個動畫的結束
Core Animaiton提供對動畫開始與結束的檢測支持。這些通知是執行所有與動畫相關的內務處理的最佳時刻。比如說你可能使用開始通知設置一些相關狀態信息,使用對應的結束通知清理這些狀態。
有兩種不同的方式獲取關於動畫狀態的通知:
Ø 使用setCompletionBlock:方法添加一個完成塊給當前的事務。當事務中的所有動畫完成后,事務將執行你的完成塊。
Ø 給CAAnimaiton對象賦值一個代理,該代理實現了animationDidStart:方法和animaitonDidStop:finished:代理方法。
如果你想將兩個動畫鏈接在一起,使得當第一個動畫結束之后啟動第二個動畫。不要使用動畫通知。相反,使用動畫對象的beginTime屬性在希望的時間啟動動畫。為了將兩個動畫鏈接在一起,設置第二個動畫的開始時間為第一個動畫的結束時間。
如何動畫有圖層支持視圖
如果一個圖層屬於一個圖層支持視圖,創建動畫的推薦做法是使用由UIKit或AppKit提供的基於視圖的動畫接口。有幾種使用Core Animation接口直接動畫圖層的方式,但如何創建這些動畫依賴於目標平台。
因為iOS視圖底層總有一個圖層,UIView類從圖層對象派生了大部分的數據。因此,對圖層屬性的更改會通過視圖對象自動表現出來。此行為代表你可以使用Core Animation或UIVIew接口完成對圖層屬性的更改。
如果你想使用Core Animation初始化動畫,你必須在一個基於視圖的動畫塊內部執行所有Core Animaiton調用。UIView類默認是關閉圖層動畫的,你在動畫塊之外所做的改變都不是動畫,但是你可在動畫塊中重新啟用圖層動畫。清單3-5展示了一個關於如何隱式改變圖層opacity和顯式改變圖層的position例子,在例子中myNewPosition變量被事先計算並被塊所捕獲。兩個動畫都開始於相同的時間,但透明度動畫使用默認的定時,而位置動畫使用動畫對象指定的定時。
清單3-5 動畫附屬在iOS視圖上的圖層
[UIView animateWithDuration:1.0 animations:^{
// Change the opacity implicitly.
myView.layer.opacity = 0.0;
// Change the position explicitly.
CABasicAnimation* theAnim=[CABasicAnimation animationWithKeyPath:@"position"];
theAnim.fromValue = [NSValue valueWithCGPoint:myView.layer.position];
theAnim.toValue = [NSValue valueWithCGPoint:myNewPosition];
theAnim.duration = 3.0;
[myView.layer addAnimation:theAnim forKey:@"AnimateFrame"];
}];
記住將更新視圖約束作為動畫的一部分
如果你使用基於約束的布局規則管理你的視圖位置,你必須移除任何可能對動畫的配置部分造成干擾的約束。約束將影響所有你對視圖的位置和尺寸的改變。也會影響到視圖和它的子視圖的關系。如果你正在動畫改變所有這些項目,你可以移除這些約束后再做改變,接着應用需要的新約束。
構建圖層層級
大多數時候,使用圖層的最佳方式是將圖層和視圖配合使用。可能你需要向視圖添加另外的圖層對象以增加視圖層級。為了獲得更佳的性能或者單獨使用視圖實現你需要的功能會比較困難時而選擇使用圖層。由此種種,所以你得明白如何管理圖層的層次。
重要:在OS X 10.8及之前的版本中,推薦的做法是最小化圖層層次的使用,盡量使用支持圖層的視圖。引進圖層重繪策略的OS X版本讓你可以自定義支持圖層的視圖的行為以及獲得與之前使用獨立圖層的那種性能。
將圖層排列為圖層層級
圖層層級在許多方式上都與視圖的層級相似。嵌入一個圖層到另一個圖層中,嵌入的圖層(稱為sublayer)與父圖層(稱為superlayer)之間將形成一個父子關系。這個父子關系會影響子圖層的許多方面。比如當圖層的內容位於它的父級內容之上,它的位置則被指定為相對於它父級的坐標系統,並且也會被任何應用於父級的變換所影響。
每個圖層對象都擁有添加、插入和移除子圖層的方法。表4-1是對這些方法和行為的總結。
行為 |
方法 |
描述 |
添加圖層 |
addSublayer: |
給當前的圖層添加一個新的子層對象。子層被加入到當前圖層的子層列表的末尾。圖層將顯示在所有與其有相同zPosition屬性值的同胞圖層的頂部。 |
插入圖層 |
insertSublayer:above: insertSublayer:atIndex: insertSublay:below: |
將子圖層插入到子圖層層次中指定的索引或者相對於其他子圖層的位置。當插入到其他圖層的上方或下方,你僅需指定子圖層在子圖層數組中的位置。圖層的可視性主要由它的zPosition屬性決定,然后是由其在子圖層數組中的位置決定。 |
移除圖層 |
removeFromSuperlayer |
將子圖層從父圖層中移除 |
交換圖層 |
replaceSublayer:with |
將某個子圖層與另外的圖層交換位置。如果你插入的圖層已經在另外的圖層層次中,首先它將從圖層之前的圖層層次中移除 |
當需要操作圖層對象時你可以使用上面表格內的的方法。你不能使用這些方法去管理屬於layer-backed視圖的圖層。然而一個layer-backed視圖可以作為你創建的獨立圖層的父圖層。
當添加和插入一個子圖層,在它顯示到屏幕之前你必須設置子圖層的尺寸和位置。你可以在子圖層被添加到圖層層次之后更改其位置與尺寸,但應該養成在創建圖層的時候就設置這些值的習慣。
你使用bounds屬性設置一個子圖層的尺寸,使用position屬性設置子圖層在它的父圖層內的位置。邊界矩形的原點幾乎總是(0,0),尺寸可以是任何尺寸值。Position屬性被解釋為相對於圖層的錨點,默認是錨點被定為在圖層的中心。如果你不給這些屬性賦值,Core Animation將設置圖層的初始寬和高都為0,圖層的position屬性值為(0,0)。
myLayer.bounds = CGRectMake(0,0,100,100);
myLayer.position = CGPointMake(200,200);
重要:你的圖層寬和高總是設置整型數。
圖層層級對動畫的影響
一些superlayer屬性會影響應用到它的子圖層的動畫的行為。speed屬性就是一個例子。對於動畫來說,speed是一個倍增器。默認該屬性的值被設為1.0,但將該值改為2.0將引起動畫運行速度是原始速度的兩倍,因此花費一半的時間就動畫就結束了。該屬性影響的不僅僅是設置該屬性的圖層也會影響圖層的子圖層。像這樣的改變也會倍增。如果子圖層和它的父圖層都擁有2.0的速度,則在子圖層上動畫的運行將是它的原始速度的4倍(盜夢空間中,現實的5分鍾在第一層夢境中是1小時,第二層夢境則是12小時)。
圖層的大部分屬性的變化僅影響所有被它包含的子圖層。比如對圖層應用一個旋轉變換將旋轉該圖層以及所有它的子圖層。相似地,改變一個圖層的透明度也會改變它的子圖層透明度。
調整你的圖層層次的布局
Core Animation對調整子圖層的尺寸和位置有多個選項,以應對它的子圖層的改變。在iOS中,layer-backed視圖的普遍使用,使得圖層層次的創建變得次要。僅僅是手動布局被支持。對於OS X,有多種輕松管理你的視圖層次的選項可用。
圖層級的布局僅僅相關與如果你正在構建的圖層層次使用的是你創建的獨立圖層。如果你的app的圖層都是與圖層相關,使用基於圖層的布局支持對你的視圖尺寸與位置的更新,以應對尺寸與位置的變化。
手動規划你的圖層層次
在iOS和OS X中,你可以通過在父圖層的代理對象上實現layoutSublayersOfLayer:方法手動處理布局。你使用該方法調整任何當前內嵌到圖層中的子圖層的位置和尺寸。當手動布局更新時,對每個子圖層的位置執行必要的計算取決於你。
如果你實現一個自定義圖層子類,你的子類可以覆蓋layoutSublayers方法並使用該方法(而不是代理)去處理任何布局任務。如果你需要對在你自定義的圖層類內的子圖層的定位進行完全的控制的情況下,你只需要覆蓋這個方法。
子圖層與裁剪
與視圖不同的,一個父圖層不會自動地裁剪超出其邊界的子圖層的內容。相反,默認情況下,父圖層允許他的子圖層完全的被顯示。然而,通過設置圖層的masksToBounds屬性為YES,你可以重新啟用裁減功能。
如果圓角半徑被指定的話,一個圖層的裁減蒙版形狀包括圖層的圓角半徑。圖4-3說明了masksToBounds屬性如何影響一個擁有圓角半徑的圖層。當masksToBounds屬性被設置為NO,整個子圖層被顯示,盡管它已經超出了它的父圖層的邊界。將masksToBounds屬性設為YES,將引起超出父圖層的子圖層內容被裁減掉。
圖4-3 子圖層的裁減區域設為父圖層的邊界
在圖層之間轉換坐標值
你可能需要將某圖層中的一個坐標值轉換為屏幕坐標位置相同而處在不同圖層的一個坐標值。CALayer提供了一組簡單的轉換方法以應對這種情況:
Ø convertPoint:fromLayer:
Ø convertPoint:toLayer:
Ø convertRect:fromLayer:
Ø convertRect:toLayer:
除了轉換點和矩形的值,你也可以在兩個圖層之間轉換時間,使用convertTime:fromLayer:與convertTime:toLayer:方法。每個圖層定義了自身的本地時空,使用本地時空與其他系統同步動畫的開始和結束。這些時空默認被同步;然而,如果你改變了圖層的動畫速度,圖層的時空也相應的發生變化。你可以使用時間轉換方法考慮這樣的因素,確保兩個圖層的時間是同步的。
高級動畫技巧
有許多方式配置基於屬性或關鍵幀的動畫。如果需要同步和異步地執行多個動畫,可以使用更高級的行為同步這些動畫的定時或將它們鏈接在一起。你可以使用其他動畫對象類型創建可視過渡,以及其他感興趣的動畫特效。
過渡動畫支持改變圖層的可視性
按字面上理解,一個過渡動畫對象為圖層創建一個可動畫的可視過渡。最普遍的過渡對象用法是動畫一個圖層的顯現和消失。與基於屬性的動畫不同,一個過渡動畫操縱一個圖層的緩存圖片以創建可視效果,如果通過單獨的改變屬性實現會非常的困難,也許根本無法實現。標准的過渡類型包括:將舊視圖移開顯示新視圖、推入、移動、淡入淡出動畫。在OS X上,你也可以使用Core Image過濾器創建使用其他特效類型,比如擦除、翻頁、波紋、或你設計的自定義特效的過渡。
為了實現一個一個過渡動畫,創建一個CATransition對象,然后添加該對象到包含過渡特效的圖層上。你使用過渡對象指定所要執行的過渡類型以及過渡動畫的開始點和結束點。你也可以不讓過渡動畫完整的執行。過渡對象讓你指定動畫運行的開始和結束進度值。這些值可以使動畫在過渡的中間點開始或結束。
清單5-1用於在兩個視圖之間創建一個可動畫的推入過渡。在例子中,myView1和myView2被放置在相同父視圖中的相同位置,但只有myView1是當前可視的。推入過渡引起myView1一邊漸隱一邊向左滑出,直到它被隱藏,而myView2從右側滑入變成可視。更新兩個視圖的隱藏屬性確保兩個視圖的可視性在動畫的結尾是正確的。對一個圖層應用推入過渡,第一個滑出的圖層的使用當前的可視性,而右側滑入的圖層使用最后修改后的可視性。
清單5-1 iOS中得兩個視圖間的過渡特效
CATransition* transition = [CATransition animation];
transition.startProgress = 0;
transition.endProgress = 1.0;
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromRight;
transition.duration = 0.5;
// 為兩個圖層加入過渡動畫
[_myView1.layer addAnimation:transition forKey:@"transition"];
[_myView2.layer addAnimation:transition forKey:@"transition"];
// 最后,改變圖層的可視性
_myView1.hidden = !_myView1.hidden;
_myView2.hidden = !_myView2.hidden;
當兩個圖層包含相同的過渡特效,你可以對兩個圖層應用同一個過渡對象。使用相同的過渡對象也簡化了代碼。然而,你也可以使用不同的過渡對象並且如果對每個圖層過渡參數都是不同的,那么就肯定需要使用兩個不同的過渡對象。
清單5-2展示了在OS X平台上,如何使用Core Image過濾器實現一個過渡特效。配置帶有你想要的參數的過濾器后,將過濾器賦值給過渡對象的filter屬性,在完成這些之后,對動畫的使用和其他類型的動畫對象的使用是一樣的。
清單5-2 在OS X平台,使用Core Image過濾器動畫一個過渡特效
// Create the Core Image filter, setting several key parameters.
CIFilter* aFilter = [CIFilter filterWithName:@"CIBarsSwipeTransition"];
[aFilter setValue:[NSNumber numberWithFloat:3.14] forKey:@"inputAngle"];
[aFilter setValue:[NSNumber numberWithFloat:30.0] forKey:@"inputWidth"];
[aFilter setValue:[NSNumber numberWithFloat:10.0] forKey:@"inputBarOffset"];
// Create the transition object
CATransition* transition = [CATransition animation];
transition.startProgress = 0;
transition.endProgress = 1.0;
transition.filter = aFilter;
transition.duration = 1.0;
[self.imageView2 setHidden:NO];
[self.imageView.layer addAnimation:transition forKey:@"transition"];
[self.imageView2.layer addAnimation:transition forKey:@"transition"];
[self.imageView setHidden:YES];
注意:當在動畫中使用Core Image過濾器時,配置過濾器是技巧性最強的地方。比方說,使用欄擦除過渡特效,指定一個輸入角度,如果角度過大或過小可能導致沒有過渡特效發生。如果你沒有看到你期望的動畫,嘗試將你的過濾器參數調整為不同的值再運行查看。
自定義動畫的定時
時間系統是動畫的一個重要部分。通過Core Animation方法和CAMediaTiming協議可為動畫指定精確的時間信息。共有兩個Core Animation類適配該協議。其中之一是CAAnimation類,所以你可以在動畫對象中指定時間信息。其二是CALayer,你可以為隱式動畫配置一些與時間相關的功能。雖然隱式事務對象包裝了這些動畫,通常優先使用所提供的默認時間信息。
當考慮時間與動畫的時候,理解圖層對象如何與時間工作是重要的。每一個圖層都有自己用於管理動畫定時的本地時間系統。一般兩個不同圖層的本地時間系統足夠的接近,你可以為每一個圖層指定相同的時間值,用戶不會察覺任何異樣。然而,圖層的本地時間系統可以被它的父圖層或它自己的定時參數更改。比如說,改變圖層的speed屬性將引起圖層或子圖層上動畫的持續時間成比例的變化。
為了幫助你確定一個圖層的適當時間值,CALayer類定義了convertTime:fromLayer:以及convertTime:toLayer:方法。你可以使用這些方法轉化一個固定的時間值到一個圖層的本地時間系統,或者將某一圖層的時間值轉換為另一個圖層的時間值。這些方法將媒體定時屬性考慮進去,這可能影響圖層的本地時間系統,返回值用於其他圖層。清單5-3展示了一個例子,你應該常規地使用獲取一個圖層的當前本地時間系統。CACurrentMediaTime函數是一個便利函數,返回計算機的當前時鍾時間,該方法用於獲取並轉換至圖層的本地時間。
清單5-3 獲取一個圖層的當前本地時間
CFTimeInterval localLayerTime = [myLayer convertTime:CACurrentMediaTime() fromLayer:nil];
一旦你在圖層的本地時間系統中有一個時間值,你可以使用該值更新與事件相關的動畫對象或圖層的屬性。使用這些定時屬性,你可以完成一些有趣的動畫行為,包括:
Ø 使用beginTime屬性設置動畫的開始時間。通常,動畫開始於下一個更新循環周期。你可以使用beginTime參數延遲動畫的開始幾秒鍾時間。該方式將鏈接在一起的兩個動畫設置某一個動畫的開始時間為另一個動畫的結束時間。
如果你延時了動畫的開始。你可能也想設置fillMode屬性為kCAFillModeBackwards。該填充模式將引起圖層顯示動畫的開始值,即使在圖層樹中的圖層對象包含了一個不同的值。沒有填充模式,你將看到動畫在開始執行前發生一次到結束值的跳躍。
Ø autoreverses屬性引起一個動畫在指定的持續時間內執行並返回到動畫的開始值。你可以將此屬性與repeatCount屬性聯合使用,讓動畫在開始與結束值之間反復來回。對自動返回動畫設置重復計數為一個整數(比如1.0)將引起動畫停止在動畫的開始值。添加一個半拍值(比如重復計數為1.5)將引起動畫停止在它的結束值。
對動畫組中的動畫使用timeOffset屬性,讓動畫一開始就出現在以后某時刻才會出現的狀態。(Lets say you have an animation with a duration of 5 seconds. Normally your animation would run from time 0 to time 5. If you set a timeOffset of 2, your animation will start out at time 2, reach time 5 and then 'wrap around' and run time 0 to time 2.)
為了暫停一個動畫,你可以利用圖層適配CAMediaTiming協議這一點,設置圖層動畫的速度為0.0.設置速度為0暫停動畫直到你改變該屬性值為一個非零值。清單5-4展示了一個簡單的關於如何暫停和恢復動畫的例子。
清單5-4 暫停和恢復一個圖層的動畫
-(void)pauseLayer:(CALayer*)layer {
CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime()
fromLayer:nil];
layer.speed = 0.0;
layer.timeOffset = pausedTime;
}
-(void)resumeLayer:(CALayer*)layer {
CFTimeInterval pausedTime = [layer timeOffset];
layer.speed = 1.0;
layer.timeOffset = 0.0;
layer.beginTime = 0.0;
CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime()
fromLayer:nil] - pausedTime;
layer.beginTime = timeSincePause;
}
顯式事務可以改變動畫的參數
對圖層屬性的每次更改都是事務的一部分。CATransaction類管理動畫的創建和分組並在適當的時間執行動畫。在大部分情況下,你不需要創建你自己的事務。無論什么時候,給圖層添加顯式或隱式動畫,Core Animation會自動創建一個隱式事務。然而你也可以創建顯式事務以能夠更精確的管理動畫。
使用CATransaction類提供的方法創建與管理事務。通過調用begin類方法,可以開始(或隱式地創建)一個新的事務;調用commit類方法可結束一個事務。兩個方法之間的代碼就是作為事務部分的變化。比如說,改變一個圖層的兩個屬性,你可以使用清單5-5所示代碼。
清單5-5 創建顯式事務
[CATransaction begin];
theLayer.zPosition = 200.0;
theLayer.opacity = 0.0;
[CATransitoin commit];
使用事務的主要一個原因是顯式事務的限制內,你可以改變動畫的持續時間,定時函數以及其他參數。你也可以賦值一個完成塊給整個事務,這樣當動畫組完成后能夠得到一個通知。改變動畫參數須要在事務字典中使用setValue:forKey:方法更改適當的鍵。比如說,為了改變默認的持續時間為10秒,則你需要改變kCATransactionAnimationDuration鍵,如清單5-6所示。
清單5-6 改變動畫默認的持續時間
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat:10.0f];
forKey:kCATransactionAnimationDuration];
[CATransaction commit];
在你想提供不同默認值給不同的動畫集合的情況下你可以內嵌事務。為了一個事務中內嵌一個事務,僅需要再次調用begin類方法。每一個begin類方法必須有一個與之配對的commit類方法。僅在你提交了對最外層事務的變化之后,Core Animation開始相關的動畫。
清單5-7顯式了一個事務中嵌套另一個事務的例子,內層的事務改變與外層的事務相同的動畫屬性,但是使用不同的值。
清單5-7 內嵌顯式事務
[CATransaction begin]; // Outer transaction
// Change the animation duration to two seconds
[CATransaction setValue:[NSNumber numberWithFloat:2.0f]
forKey:kCATransactionAnimationDuration];
// Move the layer to a new position
theLayer.position = CGPointMake(0.0,0.0);
[CATransaction begin]; // Inner transaction
// Change the animation duration to five seconds
[CATransaction setValue:[NSNumber numberWithFloat:5.0f]
forKey:kCATransactionAnimationDuration];
// Change the zPosition and opacity
theLayer.zPosition=200.0;
theLayer.opacity=0.0;
[CATransaction commit]; // Inner transaction
[CATransaction commit]; // Outer transaction
給動畫添加透視
你可以在三維空間中操縱圖層。當只對簡單的Core Animation顯示層使用一個平行投影,平行投影本質上是將場景扁平化到二維平面。該默認行為引起相同尺寸不同zPosition的圖層顯式的尺寸相同,即使是圖層離z坐標很遠。你一般在三維場景中會有一個透視的檢視口。你可以通過更改圖層的變換矩陣改變這個行為,讓動畫包含透視信息。
當更改一個場景的透視,你需要更改包含被觀察的圖層的父圖層的sublayerTransform矩陣。更改父圖層簡化了你需要給所有子圖層應用透視信息的代碼。同時也保證了透視信息被正確的應用給不同平面互相覆蓋的同胞子圖層。
清單5-8顯示了一種為父圖層創建簡單透視變換的方式。自定義eyePosition變量是沿着z坐標從觀察點到視圖圖層相對距離。通常為eyePosition指定一個正數讓圖層按照預期的方式調整。eyePosition越大,結果則是一個更加扁平的場景,而值越小將引起圖層間更加戲劇性的視覺表現。
清單5-8 為父圖層添加一個透視變換
CATransform3D perspective = CATransform3Didentity;
Perspective.m34 = -1.0 / eyePosition;
//為父圖層應用變化
myParentLayer.sublayerTransform = perspective;
Core Animation使用動作對象為圖層實現了隱式動畫行為。動作對象服從CAAction協議並定義了一些運行於圖層的相關行為。所有CAAnimation對象都實現了這個協議。無論何時,如果一個圖層對象的屬性發生變化,則這些動作對象將被分派執行。
可動畫屬性是其中一種動作類型,你可以定義幾乎任何你想要的動作。然而你需要定義你的動作對象並關聯到你的圖層對象。
自定義適配CAAction協議的動作對象
為了創建你自己的動作對象,你需要適配CAAction協議並實現runActionForKey:object:arguments:方法。在該方法中利用可用的信息執行任何你想要在圖層上的動作。你可能使用該方法給圖層添加動畫對象或者你可能使用該方法執行另外的任務。
當你定義了一個動作對象,你必須決定動作以何種方式被觸發。動作的觸發器定義了你用於注冊動作的鍵。動作對象可在下面的情況下被觸發:
Ø 圖層的某一個屬性值被改變。這可以是圖層的任何一個屬性,不僅僅是可動畫的屬性。 (你也可以給添加到圖層的自定義屬性關聯動作。)識別動作的鍵是屬性名。
Ø 圖層變成可視或被加入到圖層層次中。則識別動作的鍵為kCAOnOrderIn。
Ø 圖層從圖層層次中被移除。則識別動作的鍵為kCAOnOrderOut。
Ø 圖層是即將包含一個變換動畫。則識別動作的鍵為kCATransition。
動作對象設置給圖層才產生效果
在動作被執行之前,圖層需要找到一個相應的動作對象。與圖層相關的動作的鍵可以是被更改的屬性名或一個特殊的識別動作的字符串。當一個適當的事件發生在圖層上,圖層調用它的actionForKey:方法搜索與鍵關聯的動作對象。你的應用可以介入到搜索期間的幾個關鍵點,並提供一個與鍵相關的動作對象。
Core Animation以下面的順序搜索動作對象:
1.如果圖層有一個代理,並且代理實現了actionForLayer:forKey:方法,圖層調用該方法。代理必須完成下面所述操作之一:
l 返回給定的鍵指定的動作對象
l 如果代理不處理動作則返回nil,而搜索操作將繼續。
l 返回NSNull對象,這將引起搜索操作立即結束。
2. 圖層在圖層的action字典內搜索給定的鍵
3. 圖層在style字典中查詢一個包含鍵的動作字典。(換句話說,style字典包含一個actions鍵,它的值也是字典。圖層在第二個字典中搜索給定的鍵。)
4. 圖層調用它的defaultActionForKey:類方法。
5. 圖層執行由Core Animation定義的隱式動作(如果有)。
如果你在任何一個適當的搜索點提供了一個動作對象,圖層將停止它的搜索並執行返回的動作對象。當它找到了一個動作對象,圖層調用對象的runActionForKey:object:arguments:方法執行動作。如果你為一個給定的鍵定義的動作是一個CAAnimation類實例,你可以使用默認的方法實現執行動畫。如果你自己定義了服從CAAction協議的對象,你必須使用你的對象的方法實現做任何適當的動作。
設置動作對象的位置依賴於你打算如何更改圖層。
對於只應用在指定環境的動作,或對於已經使用代理對象的圖層,提供一個代理和實現它的actionForLayer:forKey:方法。
對於不使用代理的圖層對象,添加動作到圖層的actions字典。
與定義在圖層對象上的自定義屬性相關的動作,包括動作在圖層的style字典。
對於那些是圖層行為基礎的動作,子類化圖層並覆蓋defaultActionForKey:方法。
清單6-1 顯示了一個用於給定動作對象的代理方法實現。在這種情況下,代理搜索圖層contents屬性的改變並將新內容轉換為使用一個變換動畫的位置。
清單6-1 使用圖層代理對象提供一個動作
- (id<CAAction>)actionForLayer:(CALayer *)theLayer
forKey:(NSString *)theKey {
CATransition *theAnimation=nil;
if ([theKey isEqualToString:@"contents"]) {
theAnimation = [[CATransition alloc] init];
theAnimation.duration = 1.0;
theAnimation.timingFunction = [CAMediaTimingFunction
functionWithName:kCAMediaTimingFunctionEaseIn];
theAnimation.type = kCATransitionPush;
theAnimation.subtype = kCATransitionFromRight;
}
return theAnimation;
}
使用CATransaction類臨時禁用動作
你可以使用CATransaction類臨時禁用圖層的動作。當你改變了一個圖層的屬性,Core Animation通常會為動畫屬性的改變創建一個隱式事務對象。如果你不想以動畫的形式展現變化,你可以通過創建一個顯示事務並設置它的kCATransactionDisableActions屬性為True禁用隱式動畫。清單6-2的代碼片段 顯示了將指定的圖層從圖層數中移除時禁用動畫。
清單6-2 臨時禁用一個圖層的動作
[CATransaction begin];
[CATransaction setValue:(id)
forKey:kCATransactionDisableActions];
[aLayer removeFromSuperlayer];
[CATransaction commit];
提升動畫的性能
Core Animation是提升based-app動畫幀率的最佳方式 ,但使用Core Animation並不保證性能一定會得到提升。特別是在OS X中,你仍必須選擇最有效的方式使用Core Animation的行為。跟所有和性能相關的問題一樣,隨着時間的推移,你應該使用Instruments測量和追蹤你的應用的性能性能正在提升而不是在退化。
常規的技巧和竅門
有很多種方法可以讓你的層運行的更加有效率(因為層是核心動畫的基礎)。就像任何優化一樣,你應該先在優化前審查下現在的代碼讓動畫表現如何,這個是基准,然后才能對比出優化有沒有效果,效率是不是更高了
盡可能使用不透明圖層
設置opaque屬性為YES可以讓核心動畫知道不需要給圖層維持alpha通道,沒有alpha通道意味着就不需要渲染這層的背景內容了,從而節省了渲染時間。然后這個屬性主要是用於設置層或者以層為基礎的視圖的,也或者是用於核心動畫創造的底層bitmap層的情況。如果把一張圖像直接設置為層的contents屬性,那么這個圖像的alpha通道會被強制保留,無論你設不設定這個值。
將復雜路徑拆分成簡單路徑
CAShapeLayer類根據你提供的路徑來渲染到bitmap圖像,在合成的時候渲染成內容。這么做的優點是層總是在最佳分辨率繪制路徑,但是這個優點會消耗額外的渲染時間。如果這個路徑太復雜,渲染的代價就會很高,並且如果這個路徑的尺寸經常改變(這會導致重繪也經常發生),用於繪畫的時間也會增加,這會成為一個阻止最佳表現性能的瓶頸
有一個辦法去降低繪制形狀層的時間,就是把復雜的形狀分拆成簡單的形狀。用更簡單的路徑和多層用於CAShapeLayer對象,這比繪制一個大的復雜路徑時間快得多。因為繪制操作發生在CPU,合成工作發生在GPU,當然這種優化也取決於你的內容。因此,優化前以現有效果為基准很重要。
顯式為相同的圖層設置contents屬性
如果你在多個層對象上使用同一張圖片,自己加載圖像,把這個圖像直接分派給這些層對象的contents屬性。分配一個圖像到contents屬性可以預防分配用於內容儲備的內存空間(UIView實際並不將自己繪制到屏幕上,而是先繪制到它的圖層上,然后是圖層顯示在屏幕上,視圖並不會頻繁的重繪;相反,它的繪圖結果會被緩存起來,而繪圖的緩存版本(后備存儲)將被用到適當的地方)。反而這個層用你提供的圖像當做后備存儲。當幾個層用同樣的圖片,那么這些層就在共享一份內存而不是為自己再分配一個圖片拷貝。
總是將圖層的尺寸設置為整數值
為了最好的效果,應該將層對象的寬和高設置為整數值,雖然你使用浮點數的形式設定圖層邊界的寬高,圖層的邊界最終用於創建一個bitmap圖片。指定圖層的寬高為整型值會簡化Core Animation必要的創建和管理備份存儲與其他圖層信息的工作。
如果需要,可以使用異步的方式渲染圖層
任何在代理方法drawLayer:inContext或視圖的drawRect:方法中的繪制操作都默認是和主線程是同步的,在某種情況下,同步的繪制你的內容可能不會有最好的效果和表現。如果注意到動畫執行的不好,可能就得試試drawsAsynchronously這個層的屬性(iOS 6引入),讓這些操作在后台線程中工作。如果你這么做了,得確定繪制代碼是線程安全的,而且理所應當你應該在將異步繪圖的代碼置入你的產品代碼之前總是測試異步繪圖的性能。
當給圖層添加一個陰影指定一個陰影路徑
讓Core Animation自己決定陰影的形狀是個大的開銷且影響app的表現。相比讓它自己決定,應該用shadowPath屬性明確地指定一個陰影形狀。當你這么干的時候,核心動畫用這個形狀去繪制和緩存陰影特效。對那些從不改變或者很少改變形狀的層,這是一個很好的效果提升通過減少渲染數量。圖層樣式屬性動畫
在渲染處理期間,Core Animaiton持有圖層的不同屬性,並按順序渲染這些屬性。該順序決定了圖層最終的呈現。本章將說明通過設置不同的圖層屬性完成渲染的結果。
注意:OS X和iOS中得圖層樣式屬性是不同的,本章將有提及。
幾何屬性
一個圖層的幾何屬性指定它相對於它的父圖層被顯示的方式。幾何屬性頁指定了圖層圓角的半徑以及應用到圖層和它的子層的變換。圖A-1顯示了示例圖層的邊界矩形。
圖A-1 圖層幾何
下面列出的CALayer屬性制定了一個圖層的幾何結構:
l bounds
l position
l frame(根據bounds和position計算得出,該屬性是不是一個可動畫屬性)
l anchorPoint
l cornerRadius
l transform
l zPosition
注意:cornerRadius屬性僅在iOS 3.0及以上被支持。
背景屬性
Core Animation首先會渲染圖層的背景。你可以為背景指定一個顏色。在OS X中,你也可以指定一個應用到背景內容的Core Image過濾器。圖A-2顯示了2個示例圖層的版本。在左邊的圖層設置了backgroundColor屬性,而右邊的沒有設置背景顏色。但有一個邊框和一個應用在圖層backgroundFilters屬性上的擠壓變形濾鏡。
圖A-2 有背景顏色的圖層
背景濾鏡被應用到位於圖層背后的內容上。該內容主要是由父圖層的內容組成。你可能使用一個背景濾鏡讓前景圖層內容突出;比如說,通過應用一個模糊濾鏡。
下面的CALayer屬性會影響圖層的背景顯示:
backgroundColor
backgroundFilters(iOS不支持此屬性)
注意:在iOS中,backgroundFilters屬性在CALayer類中是被暴露的,但復制到該屬性的濾鏡是被忽略的。
圖層內容
如果圖層有任何的內容,該內容將被渲染在背景顏色的上面。你可以通過直接設置一副位圖提供圖層內容,或使用一個代理指定內容,或是子類化圖層並直接繪制內容。並且你可以使用許多不同的繪圖技術(包括Quartz、OpernGL、Quartz合成器)提供內容。圖A-3顯示了一個示例圖層,它的內容是通過直接設置一副位圖。位圖內容由一個右下角是一個機器人圖標的大透明空間構成。
圖A-3 圖層顯示一副位圖圖片
帶有圓角半徑的圖層不會自動裁剪它們的內容;然而,設置圖層的masksToBounds屬性為YES將引起圖層裁減掉圓角以外的內容。
下面的CALayer屬性影響一個圖層的內容的顯示:
Ø contents
Ø contentsGravity
Ø masksToBounds
圖層可能包含一個或更多的孩子圖層,孩子圖層被稱為子圖層(sublayer)。子圖層被遞歸地渲染並相對於父圖層的邊界矩形進行定位。另外,Core Animation應用父圖層的sublayerTransform給每一個子圖層,sublayerTransform相對於父圖層的錨點。你可以使用子圖層變換應用透視,應用於所有圖層的其他效果也是一樣的。圖A-4顯示了一個含有2個子圖層的示例圖層。左邊的版本包含了背景顏色,右邊的版本沒有背景顏色。
圖A-4 顯示子層內容的圖層
設置圖層的masktSToBounds屬性為YES將引起超出圖層邊界的任何子圖層被裁減掉。
下面的CALayer屬性將影響一個圖層的子圖層的顯示:
l sublayers
l masksToBounds
l sublayerTransform
邊框屬性
一個圖層可以使用一個指定的顏色和寬顯示一個可選的邊框。邊框按照圖層的邊界矩形,並會將圓角半徑值考慮進去。圖A-5顯示了一個添加了邊框的示例圖層。
圖A-5 顯示邊框屬性內容的圖層
下面的CALayer屬性將影響圖層邊框的顯示
l borderColor
l borderWidth
平台注意點:borderColor和borderWidth屬性僅在iOS 3.0及以上得到支持
濾鏡屬性
在OS X平台,或許你可能為圖層內容應用濾鏡效果。使用自定義的合成濾鏡指定圖層的內容與它的底部圖層內容的混合方法。圖A-6顯示了一個應用了Core Image的分色器濾鏡圖層。
圖A-6 顯示濾鏡屬性的圖層
下面的CALayer屬性指定一個圖層內容的濾鏡:
Ø filters
Ø compositingFilter
平台注意點:在iOS中,圖層忽略任何你賦值給他的濾鏡值
陰影屬性
圖層可以顯示陰影效果,並且可以配置陰影的形狀、透明度、偏移、模糊半徑。如果你不指定一個自定義的陰影形狀,陰影是在圖層的部分基礎上,陰影並不是完全透明的。圖A-7顯示了相同圖層應用紅色陰影的多種不同版本的結果。
左邊和中間的版本包含一個背景顏色,所以陰影僅是顯示在圖層邊框的周圍。然而,右邊的版本不包括背景顏色,在這種情況下,陰影將應用於圖層的內容、邊框以及子圖層。
圖A-7 顯示陰影屬性的圖層
下面的CALayer屬性影響圖層陰影的顯示:
l shadowColor
l shadowOffset
l shadowOpacity
l shadowRadius
l shaodwPath
平台注意點:shadowColor、shadowOffest、shadowOpacity和shadowRedius屬性在iOS3.2+中才得到支持。shadowPath屬性在iOS3.2及以上得到支持,OS X中10.7及以上得以支持。
不透明度屬性
一個圖層的不透明度屬性決定了多少背景內容透過圖層被顯示。圖A-8顯示了一個示例圖層,它的不透明度被設置為0.5。這將允許部分背景圖片穿透顯示出來。
圖4-8 包含不透明度屬性的圖層
下面的CALyaer屬性指定圖層的不透明度:
l opacity
蒙版屬性
你可以使用一個蒙版遮蔽所有或部分圖層內容。蒙版本身是一個圖層對象,它的alpha通道被用於決定被遮蔽的內容和被透射的內容。蒙版圖層內容的不透明區域允許下方的圖層內容穿透顯示 而透明區域部分或完全地遮蔽了下方的內容(類似於PS里面的蒙版白的為顯示區域,黑的為隱藏區域)。圖A-9顯示了由一個蒙版圖層和兩個不同的背景合成的示例圖層。左邊的版本,圖層的透明度設置為1.0。右邊的版本,塗層的透明度為0.5,導致穿透圖層蒙版部分的背景內容數量減少。
圖A-9 合成蒙版屬性的圖層
下面的CALayer屬性用於指定一個圖層的蒙版
l mask
平台注意:蒙版屬性在iOS3.0及以上版本得以支持
可動畫屬性
大部分的CALayer與CIFilter的屬性是可動畫的。該附錄列出了這些屬性,以及默認使用的動畫。
CALayer可動畫屬性
表B-1 列出了你可能考慮動畫的CALayer類的屬性。表中也列出了每一個屬性的默認動畫對象類型,默認的動畫對象被創建以執行一個隱式動畫。
表B-1 圖層屬性和它的默認動畫
表B-2 列出了用於默認的基於屬性的動畫的動畫屬性
表B-2 默認隱式的基本動畫
描述 |
值 |
類 |
CABasicAnimation |
持續時間 |
0.25秒,或是當前事務的持續時間 |
鍵路徑 |
設置圖層的屬性名 |
表B-3列出了默認基於過渡動畫的動畫對象的配置
表B-3 默認隱式過渡
描述 |
值 |
類 |
CATransition |
持續時間 |
0.25秒,或當前事務的持續時間 |
類型 |
減淡(kCATransitionFade) |
開始進度 |
0.0 |
結束進度 |
1.0 |
CIFilter可動畫屬性
Core Animation添加下面的可動畫屬性到Core Image的CIFilter類中。這些屬性僅在OS X中有效。
name
enabled
鍵值編碼擴展
Core Animation擴展了NSKeyValueCoding協議,因為它與CAAnimation、CALayer有關。擴展包括為一些鍵添加了默認的值、擴展了包裝轉換,並加入了對CGPoint、CGRect、CGSize和CATransform3D類型的鍵路徑支持。
鍵值編碼適應的容器類
CAAnimation和CALayer是鍵值編碼適應的容器類,容器類的意思是你可以為任意的鍵設置值。即使有些鍵在CALayer中沒有聲明,你仍然可以像下面一樣設置值:
[theLayer setValue:@50 forKey:@”someKey”];
你可以像檢索其他鍵路徑一樣檢索任意鍵的值。比如為了檢索之前設置的someKey鍵的值,可以像下面代碼一樣操作:
someKeyValue = [theLayer valueForKey:@”someKey”];
支持的默認值
依靠一個類為沒有設置值的鍵提供一個默認值,Core Animation為鍵值編碼加入了一層轉換操作。CAAnimation和CALayer類支持這層轉換,你可以使用defaultValueForKey:類方法。
為了為鍵提供默認值,創建一個待操作類的子類,並覆蓋它的defaultValueForKey:方法。在該方法內你應該檢測傳入的鍵參數,並返回適當的值。清單C-1給出了一個defaultValueForKey:方法的實現,該方法是為一個圖層對象的maskToBounds鍵提供默認的值。
清單C-1 defaultValueForKey:實現例子:
+ (id)defaultValueForKey:(NSString *)key
{
if ([key isEqualToString:@"masksToBounds"])
return [NSNumber numberWithBool:YES];
return [super defaultValueForKey:key];
}
包裝轉換
當鍵的值的數據類型是一個標量或C語言的結構體,你必須在將值賦值到圖層之前將值包裝成對象。當訪問這類類型的值的時候,你必須將檢索到得對象使用正確的類的擴展,將對象解包為正確的值。表C-1列出了常用的C語言數據類型以及包裝這些類型的Objective-C類。
表C-1 包裝C語言數據類型的包裝器類
C語言數據類型 |
包裝類 |
CGPoint |
NSValue |
CGSize |
NSValue |
CGRect |
NSValue |
CGTransform3D |
NSValue |
CGAffineTransform |
NSAffineTransform(僅適用OS X) |
鍵路徑對結構體的支持
CAAnimation和CALayer類可以使用鍵路徑訪問已選擇的結構體域。該功能讓指定結構體的域變得非常方便。你也聯合使用setValue:forKeyPath:和valueForKeyPath:方法設置和獲取結構體的域。
CATransform3D鍵路徑
可以使用鍵路徑支持檢索包含一個CATransform3D數據類型的屬性的指定變換值。為了為一個圖層的變換指定完整的鍵路徑,你可使用字符串值transform或sublayerTransform,后面再跟上表C-2中其中一個域作為鍵路徑。比如,為了指定一個圍繞圖層z軸的旋轉因子,你可以指定一個transform.rotation.z的鍵路徑。
表C-2 變換域的鍵路徑
域的鍵路徑 |
描述 |
rotation.x |
設置一個NSNumber類型的繞x方向旋轉的弧度值。 |
rotation.y |
設置一個NSNumber類型的繞y方向旋轉的弧度值。 |
rotation.z |
設置一個NSNumber類型的繞z方向旋轉的弧度值。 |
rotation |
設置一個NSNumber類型的繞z方向旋轉的弧度值。該設置和ratation.z是功能是一樣的。 |
scale.x |
設置一個NSNumber對象,它的值是在x軸上縮放因子。 |
scale.y |
設置一個NSNumber對象,它的值是在y軸上縮放因子。 |
scale.z |
設置一個NSNumber對象,它的值是在z軸上縮放因子。 |
scale |
設置一個NSNumber對象,它的值是三個縮放因子的平均值。 |
translation.x |
設置一個NSNumber對象,它的值是x軸方向的平移因子。 |
translation.y |
設置一個NSNumber對象,它的值是y軸方向的平移因子。 |
translation.z |
設置一個NSNumber對象,它的值是z軸方向的平移因子。 |
translation |
設置一個包含NSSize或CGSize數據類型的NSValue對象。該數據類型表明在x和y軸上的平移數量。 |
接下來的例子顯示了如何使用setValue:forKeyPath方法更改一個圖層。該例子設置x軸的平移因子為10點,圖層將按指定的坐標軸方向平移給定的點數量。
[myLayer setValue:[NSNumber numberWithFloat:10.0] forKeyPath:@”transform.translation.x”];
注意:使用鍵路徑設置值與使用Objective-c屬性設置並不完全一樣。你不能使用屬性符號設置變換值。你必須使用帶有之前介紹的參數的setValue:forKeyPath:方法。
CGPoint鍵路徑
如果給定的屬性的值是CGPoint數據類型,你可以選擇表C-3其中的一個域名追加給屬性以獲得值或設置值 。比如說,為了改變圖層position屬性的x域,你可以將鍵路徑寫成position.x。
表C-3 CGPoint數據結構
結構域 |
描述 |
x |
一個點的x坐標。 |
y |
一個點的y坐標。 |
CGSize鍵路徑
如果給定的屬性值是一個CGSize數據類型,你可以選擇表C-4中其中一個域名追加給屬性以獲得值或設置值。
表C-4 CGSize數據結構域
結構域 |
描述 |
width |
size的width屬性。 |
height |
size的height屬性。 |
CGRect鍵路徑
如果給定的屬性是CGRect數據類型值,你可以選擇表C-5中其中一個域名追加給屬性以獲取或設置值。比如,為了改變一個圖層邊界屬性的寬度屬性,你可以將鍵路徑寫成bounds.size.width。
表C-5 CGRect數據結構的域名
結構體域 |
描述 |
orign |
矩形的原始點 |
origin.x |
矩形原始點的x坐標 |
origin.y |
矩形原始點的y坐標 |
size |
矩形的尺寸 |
size.width |
矩形尺寸的寬度屬性 |
size.height |
矩形尺寸的高度屬性 |
完
如果喜歡此文,記得點擊文章下方的推薦,以讓更多的人有所收獲。
文章中如有錯誤或不當之處望不吝指出,謝謝!
我的郵箱和微博: xdreamarshal@gmail.com, http://weibo.com/xdream86