Core Animation的使用

關於Core Animation

Core Animation是IOS和OS X的圖形渲染和動畫的基礎設施,你可以使用它來進行動畫繪製視圖和其他APP的可視元素。Core Animation爲你完成大量動畫幀的描繪工作。你所要做的是設置一些動畫參數(例如開始和結束點)和啓動Core Animation。Core Animation會完成剩下並把大量的實際繪製工作轉移到圖形硬件以加速渲染。這種自動的圖形加速會形成高幀率和平滑的動畫而且不會加重CPU和變慢APP。

如果你編寫IOS APP,不管你知道不,你正在使用Core Animation。如果你編寫OS X APP,你只需要進行少許操作就可以利用Core Animation。Core Animation位於APPKit和UIKit之下而且緊密集成到視圖的Cocoa和Cocoa Touch工作流。當然,Core Animation會有接口去擴展已存在的視圖動畫功能並允許你更細粒度的控制APP動畫。


Core Animation 基礎

Core Animation提供一個通用的動畫繪製視圖和其他APP可視元素的系統。Core Animation不會替代你的APP視圖。它和視圖緊密集成並提供更好的性能和動畫繪製內容。它通過緩存視圖內容爲能直接被圖形硬件使用的位圖來實現這些功能。在某些情況下,這種緩存行爲可能需要你重新考慮怎樣呈現和管理APP內容,但大多數情況下,在你使用Core Animation時,不用知道這些內容。除了緩存視圖內容,Core Animation也定義一種方式來指定任意內容,集成這些內容到你的視圖並和其他任何內容一起動畫。

你使用Core Animation去動畫改變APP的視圖和可視對象。大多數變化是與修改可視對象相關。例如,你可能使用Core Animation來動畫改變視圖位置,大小或者透明度。當你進行這樣的改變,Core Animation會在屬性的當前值和你設置的新值之間進行動畫。你通常不會使用Core Animation去每秒60次替換視圖內容,例如,卡通。而是,你使用Core Animation來移動、漸入、漸出屏幕的視圖內容,應用任意圖形轉換到視圖,或者改變視圖的其他可視屬性。

圖層提供基本的繪製和動畫

圖層對象是3D空間的2D表面,它是Core Animation的中心對象。圖層像視圖一樣管理幾何、內容和表面的可視屬性的信息。圖層不像視圖會定義自己的外觀。層僅僅管理位圖的狀態信息。位圖可能是視圖繪製或者設置圖像的結果。由於這個原因,APP的主層被認爲是模型對象,因爲它們主要管理數據。這個概念很重要,因爲這影響動畫的行爲。

基於圖層的繪製模型

大多數圖層不會做實際的繪製工作。而是,圖層計算APP提供的內容並緩存成位圖,這有時候也叫做備用存儲。當你進行一系列圖層屬性改變,你所做的就是改變關聯圖層對象的狀態信息。當一個改變觸發動畫,Core Animation傳遞圖層的位圖和狀態信息到圖形硬件,圖形硬件會使用新的狀態信息來渲染位圖。在硬件處理位圖會比軟件更快。


因爲它操作靜態位圖,基於圖層的繪製與傳統的基於視圖繪製非常不同。基於視圖繪製會經常調用視圖的drawRect:方法來使用新的參數並重新繪製內容。使用這種方式是昂貴的,因爲它在主線程使用CPU。Core Animation會盡量通過硬件操作緩存位圖來實現相同或者相似的結果以避免這種昂貴操作。

雖然Core Animation儘可能使用緩存內容,你的APP必須提供初始內容並時常更新它。

基於圖層的動畫

圖層的數據和狀態信息是和屏幕內的圖層內容的可視呈現解耦。這種解耦讓Core Animation可以插入它們並動畫改變舊狀態到新狀態。例如,改變層的位置屬性會導致Core Animation移動層從當前位置到新的位置。相似改變其他屬性也會造成適當的動畫。


Core Animation會在硬件完成所有的幀繪製。你只需要指定動畫的開始點和結束點並讓Core Animation完成剩下的工作。你可以指定自定義時間信息和動畫參數。而然,如果你不指定這些信息,Core Animation會提供默認值。

圖層對象定義自己的幾何

圖層的其中一個工作就是管理自己內容的可視幾何。不管圖層是否已經被旋轉、縮放、轉換,可視幾何封裝了內容的邊界信息。圖層像視圖一樣有框架和邊界矩形,你可以使用它來放置圖層和圖層的內容。圖層也有視圖沒有的其他屬性,例如錨點,它定義了操作的點。你指定圖層的幾何的方式也和視圖不一樣。

圖層使用兩種類型的座標系統

圖層利用基於點和單位的座標系統來指定內容的放置。使用哪個座標系統依賴於傳達的類型信息。當指定的值可以直接映射到屏幕座標或者值必須相對另一個圖層,例如圖層的點屬性,基於點的座標系統會被使用。當值不應該綁定到屏幕座標,因爲它接收其他一些值,單位座標會被使用。例如,圖層的anchorPoint屬性指定相對圖層本身邊界的點而且它能被改變。

通常使用點座標來指定圖層的大小和位置,你使用圖層的bounds和position屬性來設置。bounds定義圖層本身的座標系統和包含圖層在屏幕的大小。position屬性定義圖層相對於父座標系統的位置。雖然層有frame屬性,這個屬性實際是從bounds和position屬性的值衍生出來並且它很少使用。

圖層的bounds和frame矩形的方向總是匹配默認底層平臺的方向。


上面的例子顯示position位於圖層的中心點。這個屬性是其中一個會被圖層的anchorPoint屬性改變的屬性。

錨點是其中一個使用單位座標系統的屬性。Core Animation使用單位座標系統代表哪些當圖層的大小改變時,值也會改變的屬性。你可以認爲單位座標是一個百分比的值。單位座標空間的座標在0.0到1.0範圍內。


所有座標值,無論是點座標還是單位座標都是浮點型。使用浮點型允許你指定精確的位置。

錨點影響幾何操作

圖層的幾何操作是相對於圖層的錨點。當操作圖層的position或者transform屬性,錨點的影響是最明顯的。position屬性總是和圖層的錨點相關的而且你應用到圖層的transformations也於錨點相關。



圖層可以在3維上操作

每個圖層都有2個轉換矩陣,你可以使用它們來操作圖層和圖層的內容。CALayer的transform屬性指定你想應用在圖層和圖層的子圖層的轉換。通常,當你想要改變圖層本身時,你會使用這個屬性。例如,你可能使用這個屬性來縮放或者旋轉圖層或者臨時地改變位置。sublayerTransform屬性定義額外的轉換,它只應用於子圖層並且通常用於給場景的內容添加一個透視效果。

轉換是通過矩陣與座標值相乘得到新的座標值,這個新的座標值代表原始點的轉換版本。因爲Core Animation的值能夠在3維上指定,每個座標點都有4個值,它需要乘於一個4*4的矩陣。在Core Animation,這個轉換用CATransform3D代表。幸運地是,你不用直接修改這個結構體的成員來實現標準的轉換。Core Animation提供一套綜合的函數來創建縮放、轉換、旋轉矩陣和矩陣比較。除了使用這些函數來操作轉換,Core Animation擴展KVC支持來允許你使用鍵路徑修改轉換。


下面展示常用的轉換矩陣設置。任何座標乘於一個恆等轉換(identity transform),返回相同的座標。對於其他轉換,座標的改變依賴於你改變哪個矩陣的組件。例如,只是在x軸平移,你爲轉換矩陣的tx組件提供一個非0值並且讓ty和tz的值爲0。對於旋轉,你提供適當的正弦和餘弦值的目標旋轉角度。


圖層樹反映了動畫狀態的不同方面

Core Animation有3組圖層對象。

• 模型圖層樹(或者簡稱“圖層樹”)的對象是其中一個交互最多的對象。樹的對象都是模型對象並且存儲任何動畫的目標值。不管什麼時候改變圖層的屬性,你都使用這些對象。

• 展示樹的對象包含任何運行動畫的動態值。然而這個圖層樹的對象包含動畫的動態值,展示樹的對象反映屏幕出現的當前值。你不應該修改這個樹的對象。而是,你使用這些對象讀取當前動畫值,可能使用這些值來創建新的動畫。

• 渲染樹的對象執行實際的動畫而且在Core Animation是私有的。

每組圖層對象都組織成像視圖一樣的層次結構。實際上,APP會激活所有視圖的圖層,每個樹的初始結構完全匹配視圖的層次結構。而然,APP能添加額外的圖層對象-也就是說,根據需求,圖層不關聯一個視圖的層次結構。在爲不需要全部視圖開銷的APP內容進行優化的情況下,你可能進行這種操作。


每個圖層樹的對象,都有一個匹配的對象在展示和渲染樹。就像前面所提到的,APP主要和圖層數的對象工作,但是可能同時訪問展示樹的對象。具體的說,獲取圖層對象的presentationLayer屬性,會返回對應的展示樹的對象。你可能想要訪問這些對象來讀取動畫中的當前屬性值。


注意:你應該在動畫執行中訪問展示樹的對象。當動畫正在進行,展示樹的對象值是它們在屏幕上的即時值。這種行爲不同於圖層樹,它總是反映代碼最後設置的值而且等價於動畫的最終狀態。

圖層與視圖的關係

圖層不會替代APP的視圖,也就是說,你不能單獨依賴一個圖層對象來創建可視的界面。圖層爲視圖提供基礎設施。具體的說,圖層使得描繪和動畫顯示視圖內容更加簡單和有效而且維持高幀率。而且,這裏有很多圖層不能做的事情。圖層不能處理事件,描繪內容,參與響應鏈或者做其他事情。由於這個原因,所有APP任然必須有一個或者多個視圖來處理這些交互。

在IOS,每個視圖都存儲對應的圖層,但在OS X,你必須決定哪個視圖有圖層。在OS X v10.8之後,可以爲每個視圖添加圖層。而且,你不必做這些事情並且可以在你不需要這些開銷時失效圖層。圖層會有一些內存開銷,但是它們的好處大於這些內存開銷。所以在你失效圖層支持前,進行性能測試。

當你支持視圖的圖層支持,你創建的是一個存儲圖層視圖(layer-backed)。在一個存儲圖層視圖,系統會創建底層的圖層對象並保存和同步到視圖。所以的IOS視圖和大多數OS X的視圖都是存儲圖層視圖。然而,在OS X,你可以創建主持圖層視圖(layer-hosting),這個視圖需要你自己提供圖層對象。對於主持圖層視圖,AppKit不會進行圖層處理而且不會在視圖改變時修改它。

注意:對於存儲圖層視圖,建議你儘可能操作視圖而不是圖層。在IOS,視圖只是簡單地包裝圖層對象,所以你進行的任何操作,通常對圖層都能很好的工作。

除了圖層與視圖關聯,你可以創建不關聯視圖的圖層對象。你可以嵌入這些獨立的圖層對象到APP的其他圖層對象,包括那些關聯視圖的圖層對象。你可以使用獨立的圖層對象作爲特殊優化的一部分。例如,如果你使用相同的圖像在多個地方,你可以加載一次圖像並關聯到單獨的獨立的圖層而且添加到圖層樹。每個圖層都引用圖像源而不是在內存中創建圖像的副本。

建立圖層對象

圖層對象是Core Animation的中心對象。圖層管理APP的可視內容並提供選項去修改樣式和內容的可視外觀。雖然IOS自動激活圖層支持。OS X APP需要在充分利用性能優勢前明確的激活它。一旦激活,你需要理解怎樣設置和操作圖層來獲取你想要的效果。

改變關聯視圖的圖層對象

存儲圖層視圖默認創建CALayer實例,大多數情況下,你不需要不同類型的圖層對象。而且,Core Animation提供不同的圖層類別,每一個類都提供特別的功能。選擇不同的圖層類別可能確保提高性能或者支持特殊類型的內容在相同的方式。例如,CATiledLayer類是爲展示大圖像進行有效優化的圖層。

使用UIView改變圖層的類

你可以通過重寫視圖的layerClass方法並返回不同的類對象來改變視圖的圖層對象類型。大多數IOS視圖創建CALayer對象並使用這個圖層來存儲內容。對於大多數視圖,默認的選擇也是好的一個而且你應該不需要改變它。但是你可能發現不等的圖層類別在某些情況下會更適合。例如,你可能在下面一些情況下,改變圖層的類別:

• 你的視圖使用Metal或者OpenGL ES描繪視圖,在這種情況下,你應該使用CAMetalLayer或者CAEAGLLayer對象。

• 有性能更好的圖層類別。

• 你想要利用一些特別的Core Animation圖層類別,例如特別的發射器和替換器。

改變視圖的圖層類別是非常直接的。你所要做的是重新layerClass方法並返回你想要使用的類對象。在展示之前,視圖會調用layerClass方法並使用這個類來創建新的圖層對象。一旦創建,視圖的圖層對象不能被改變。

+ (Class) layerClass {
   return [CAMetalLayer class];
}
不同的圖層類別提供特殊的行爲

Core Animation定義更多的標準圖層類別,每個圖層類別爲特殊用途定義的。CALayer類是所有的圖層對象的根類。它定義所有圖層對象必須支持的行爲而且也是存儲圖層視圖的默認類型。然而,你可以定義下面其中一個圖層類別。


提供圖層內容

圖層是數據對象並且管理APP提供的內容。圖層內容是由包含需要展示的可視數據的位圖組成。你可以通過3種方式提供內容的位圖:

• 直接賦值圖像對象(image)給圖層對象的contents屬性。(圖層內容不會或者很少改變)

• 賦值代理對象(delegate)給圖層對象並讓代理描繪圖層的內容。(圖層內容週期性的改變和可能由外部的對象提供,例如,視圖)

• 定義圖層的子類並重寫其中一個描繪方法來自己提供圖層內容。(如果你必須創建自定義圖層子類或者你想要改變基本的圖層繪製行爲)

只有在你自己創建圖層對象才需要考慮圖層內容。如果你的APP除了存儲圖層視圖外不包含其它內容。你不必考慮使用之前提到的方式來提供圖層內容。存儲圖層視圖會自動地爲關聯的圖層對象提供內容而且這可能是最有效的方式。

使用圖像來提供圖層內容

因爲圖層只是個容器而且關聯圖像位圖,你可以直接賦值圖像到圖層的contents屬性。賦值圖像給圖層是容易的並且允許你指定展示在屏幕上的圖像。這個圖像你可以直接提供而且不需要創建圖像的副本。當你使用相同的圖像在多個地方,這種方式可以節約內存。

你賦值的圖像必須是CGImageRef類型。(在OS X v10.6之後,你可以賦值NSImage對象)當賦值圖像,記住提供的圖像需要匹配本地設備的分別率。對於設備的視網膜展示,需要你調整圖像的contentsScale屬性。

使用代理來提供圖層內容

如果你的圖層內容是動態變化的,你可以在需要時使用代理對象來提供和更新內容。在展示時,圖層調用代理的方法來提供需要的內容。

• 如果你的代理對象實現displayLayer: 方法,方法的實現需要創建一個位圖並賦值給圖層的contents屬性。

• 如果你的代理對象實現drawLayer:inContext: 方法,Core Animation創建位圖和描繪位圖的圖像上下文,之後,會調用代理方法來填充位圖。你的代理方法所要做的是把內容描繪到圖形上下文。

代理對象必須實現displayLayer: 或者drawLayer:inContext: 方法。如果代理對象都實現這兩個方法,圖層只會調用displayLayer: 方法。

重寫displayLayer: 方法非常適用於在需要加載和創建位圖。

- (void)displayLayer:(CALayer *)theLayer {
    // Check the value of some state property
    if (self.displayYesImage) {
        // Display the Yes image
        theLayer.contents = [someHelperObject loadStateYesImage];
    }
    else {
        // Display the No image
        theLayer.contents = [someHelperObject loadStateNoImage];
    }
}
如果你沒有預先渲染好的圖像或者有幫助對象創建位圖,你的代理可以使用drawLayer:inContext: 方法來動態描繪內容。
- (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: 方法來描繪你的內容。

提供子類來提供圖層的內容

如果你實現自定義圖層類別,你可以重寫圖層的描繪方法來完成任何描繪。自己生成自定義內容對於圖層對象不常用,但是圖層可以管理展示的內容。例如,CATiledLayer類通過把分隔成小圖片並獨立地渲染它們來管理大圖片。因爲只有圖層有小圖片(在給定時間需要被渲染)的信息而且直接管理描繪行爲。

在創建子類時,你可以使用下面的一個方式來描繪圖層內容:

• 重寫圖層的display方法並使用它來直接設置圖層的contents屬性。

• 重寫圖層的drawInContext: 方法並使用它來直接描繪到圖形上下文。

你需要重寫哪個方法依賴於你需要怎樣控制描繪操作。display方法是主要的入口來更新圖層內容,所以重寫這個方法來讓你可以完全控制這個過程。重寫這個方法也意味着你需要創建CGImageRef並賦值到contents屬性。如果你只是描繪內容(或者讓圖層管理描繪操作),你可以重寫drawInContext: 方法並讓圖層創建內容存儲。

調整你提供的內容

當你賦值圖像到圖層的contents屬性,圖層的contentsGravity屬性決定圖像怎樣被操作來適應當前邊框。默認,如果圖像比當前邊框大或者小,圖層對象會縮放圖形來適應可用空間。如果圖層的邊框的寬高比不同於圖像的寬高比,這可能造成圖像扭曲。你可以使用contentsGravity屬性來確保你的內容以最好的方式展示。

contentsGravity屬性的值可分爲2類:

• 基於點的重力常量允許你固定圖像到圖層邊框的邊或者拐角而且沒有縮放圖像。

• 基於縮放的重力常量允許你使用其中一個選項來拉伸圖像,其中一些會存儲寬高比,有些不會。

下面展示基於點的重力設置是怎樣影響圖像。kCAGravityCenter常量,每個常量會固定圖像到圖層的邊框矩形的邊或者拐角。kCAGravityCenter常量會居中圖層的圖像。這些常量並都不會造成圖像縮放,所以圖像總是按原始大小描繪。如果圖像大於圖層的邊框,這可能會造成圖像的部分內容被裁剪,如果圖像小於邊框,部分圖層內容不會被圖像覆蓋而是顯示圖層的背景顏色(如果設置的話)。


下面展示的是基於縮放的重力常量是怎樣影響圖像。如果圖像不符合圖層的邊框矩形,那麼所有這些常量都會縮放圖像。這些模式的不同點在於怎樣處理圖像原始的寬高比。一些模式會保留,一些不會。默認,圖層的contentsGravity屬性被設置爲kCAGravityResize常量,它是唯一的不會保留圖像寬高比的模式。


使用高分辨率的圖像

圖層不會知道設備屏幕的分辨率。圖層簡單地存儲位圖的點並總是儘可能用可用的像素來最好地展示它。如果你賦值圖像到圖層的contents屬性,你必須通過設置圖層的contentsScale屬性來告訴Core Animation圖像像素。這個屬性的默認值是1.0,對應使用標準的屏幕分辨率來展示圖像。如果你打算使用視網膜展示圖像,設置這個值爲2.0。

僅在直接賦值位圖到圖層時,才需要改變contentsScale屬性。UIKit和AppKit的存儲圖層視圖會根據屏幕的分辨率和視圖的內容自動設置圖層的縮放因素爲適當的值。

調整圖層的可視樣式和外觀

圖層對象會構建可視的裝飾,例如,邊框和背景顏色,你可以使用裝飾來補充圖層的主要內容。因爲這些可視的裝飾不需要你進行渲染,在一些情況下,它們可能使用圖層作爲單獨的實體。你所要做的是設置圖層的屬性,圖層會進行必要的描繪,包括任何動畫。

圖層有自己的邊框和背景顏色

圖層可以在圖像內容中展示填充的背景和繪畫的邊框。背景顏色在圖層的圖像內容後面渲染,邊框在圖像的前面渲染。如果圖層包含子圖層,它們也會出現在邊框的子圖層。因爲背景顏色在圖像的後面,這個顏色會在圖像的透明部分顯示出來。


 下面展示設置圖層的背景顏色和邊框。

myLayer.backgroundColor = [NSColor greenColor].CGColor;
myLayer.borderColor = [NSColor blackColor].CGColor;
myLayer.borderWidth = 3.0;

注意:你可以使用任何類型的顏色來設置背景顏色,包括有透明度的顏色或者一個圖案圖像。當使用圖案,儘管如此,請注意Core Animation會處理圖案的渲染並使用標準的座標系統;這個座標系統不同於IOS的默認的座標系統。因此,在IOS渲染的圖像會出現上下顛倒的問題,除非你翻轉座標系。

如果你設置圖層的背景顏色爲不透明的顏色,考慮設置圖層的不透明屬性爲YES。這樣會提高性能並消除了圖層對透明信號的存儲管理。當拐角的弧度不爲0時,你不能標記圖層爲不透明。

圖層支持拐角弧度
你可以添加拐角弧度來創建圓角矩形。拐角弧度是一個可視的圓角裝飾。因爲它涉及到透明遮蓋,拐角弧度不會影響圖層內容的圖形,除非設置masksToBounds屬性爲YES。然而,拐角弧度總是影響圖層的背景顏色和邊框的描繪。

爲了應用拐角弧度到圖層,設置圖層的cornerRadius屬性。弧度的值會以點來測量並應用到所有的四個拐角。
圖層支持陰影嵌入

CALayer類包含幾個屬性來設置陰影。陰影通過使圖層看起來好像是浮動在它的底圖層內容之上來增加深度。這是另外的可視裝飾類型。你可以控制陰影顏色、相對圖層的內容放置位置、不透明度、形狀。

不陰影的透明度默認爲0;它可以高效地隱藏陰影。改變不透明度的值爲非0值會造成Core Animation描繪陰影。因爲默認直接放置在圖層的下面,你可能需要改變陰影的偏移量才能看見它。你設置的陰影偏移量是基於圖層本地的座標系統,它和IOS、OS X不同。


當添加陰影到圖層,陰影成爲圖層內容的一部分並擴展到圖層邊框的外面。結果,如果你激活圖層的masksToBounds屬性,陰影會被裁剪周圍的邊。如果圖層包含任何透明內容,這會造成那部分在圖層下面的陰影顯示出來並擴展到圖層沒有顯示的地方。如果你想要使用陰影和邊框遮蓋,你可以使用兩個圖層而不是一個。把遮蓋應用到包含內容的圖層並嵌入到同樣大小的、帶有陰影效果的圖層的裏面。

添加圖層的自定義屬性

CAAnimation和CALayer類擴展了KVC約定並支持自定屬性。你可以使用KVC來添加圖層的數據並通過自定義鍵來獲取這個數據。你甚至可以爲你的自定義屬性添加關聯的動作,以便當你改變這個屬性,對應的動畫會觸發。

打印存儲圖層視圖的內容

在打印過程中,圖層會在需要適應打印環境時重繪自己的內容。然而,當繪製到屏幕,Core Animation通常依賴緩存的位圖,它也會在打印時重繪內容。特別是,如果存儲圖層視圖使用drawRect: 方法來提供圖層內容,Core Animation會再次調用drawRect: 方法來打印生成打印圖層內容。

動畫展示圖層內容

Core Animation提供基礎設施來簡單地創建圖層和擁有圖層的視圖的複雜動畫。

簡單改變圖層的屬性造成的動畫

你可以根據你的需要來隱式或者顯式地展示簡單動畫。隱式動畫使用默認的時間和動畫屬性來展示動畫,而顯式的動畫需要你使用動畫對象來設置這些屬性。所以隱式動畫適合你不需要編寫很多代碼和使用默認時間來執行動畫變化。

簡單動畫涉及到改變圖層的屬性和Core Animation執行這些變化的時間。圖層定義很多能夠影響圖層的可視外觀的屬性。改變這些屬性的其中一個,是一個動畫變化外觀的方式。例如,改變圖層的不透明度從1.0到0.0會造成圖層漸出並透明。

爲了觸發隱式動畫,你必須做的是更新圖層對象的屬性。當改變圖層樹的圖層對象,你的改變會通過這些對象馬上反映出來。然而,圖層對象的可視外觀不會馬上改變。Core Animation會使用你的改變來作爲觸發器來創建和安排一個或者多個隱式動畫執行。因此,下面展示的例子會造成Core Animation創建一個動畫對象並安排這個動畫在下一個更新循環執行。

theLayer.opacity = 0.0;

爲了讓顯式動畫實現相同的效果,創建CABasicAnimation動畫對象並使用這個對象來設置動畫參數。你可以在添加動畫對象到圖層之前,設置動畫的開始和結束值,改變持續時間或者改變任何其他動畫參數。下面展示怎樣使用動畫對象來漸出一個圖層。當創建這個對象,你指定需要動畫的屬性的鍵路徑並設置動畫參數。爲了執行動畫,你使用addAnimation:forKey: 方法來添加到你想要動畫的圖層。

CABasicAnimation* fadeAnim = [CABasicAnimation animationWithKeyPath:@"opacity"];
fadeAnim.fromValue = [NSNumber numberWithFloat:1.0];
fadeAnim.toValue = [NSNumber numberWithFloat:0.0];
fadeAnim.duration = 1.0;
[theLayer addAnimation:fadeAnim forKey:@"opacity"];
 
// Change the actual data value in the layer to the final value.
theLayer.opacity = 0.0;

提示:當創建顯式動畫,建議你總是設置動畫對象的formValue屬性值。如果你沒有指定這個屬性值,Core Animation會使用當前值來作爲開始值。如果你已經更新屬性到最終值,可能並不是你想要的結果。

不像隱式動畫會更新圖層對象的數據值,顯式動畫不會修改圖層樹的數據。顯式動畫只是提供動畫。在動畫結束後,Core Animation移除動畫並使用當前數據值來重繪圖層。如果你想要顯式動畫的改變能夠永久,你必須像上面的例子更新圖層的屬性值爲最終值。

隱式和顯式的動畫通常在當前運行循環結束後執行而且當前線程必須有運行循環安排動畫執行。如果你改變多個屬性或者如果你添加多個動畫對象到圖層,所有這些屬性改變會在同一時間動畫改變。例如,你可以在同時設置兩個動畫來實現在圖層移出屏幕時漸退(fade)圖層。然而,你也可以設置動畫對象的開始時間。

使用關鍵幀動畫來改變圖層屬性

相對於基於屬性動畫會從開始值到結束值來改變屬性,CAKeyframeAnimation對象允許你指定一組線性或者非線性的目標值。關鍵幀動畫會保存一組目標值和對應每一個值執行的時間。使用簡單的設置,使用一個數組來同時指定值和時間。爲了改變圖層的位置,你可以根據路徑來進行改變。動畫對象使用你提供的關鍵幀並在指定的時間段內從一個值到下一個值之間插入值來構建動畫。

下面展示圖層position屬性的5秒動畫。position會跟隨路徑進行動畫,這個路徑使用CGPathRef數據類型。 

// create a CGPath that implements two arcs (a bounce)
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;
 
// Create the animation object, specifying the position property as the key path.
theAnimation=[CAKeyframeAnimation animationWithKeyPath:@"position"];
theAnimation.path=thePath;
theAnimation.duration=5.0;
 
// Add the animation to the layer.
[theLayer addAnimation:theAnimation forKey:@"position"];
指定關鍵幀值

關鍵幀的值是關鍵幀動畫的最重要部分。這些值定義了動畫執行過程中的行爲。重要方式指定關鍵幀值是一個包含CGPoint數據類型的數組對象,你也可以指定CGPathRef數據類型。

當指定一個數組值,數組裏面的值類型依賴於屬性。你可以直接向數組添加對象;然而,一些對象必須強轉換爲id類型,所有的數值類型或者結構體必須包裝爲對象。

• CGRect類型需要包裝爲NSValue對象。

• 對於轉換屬性,CATransform3D矩陣需要包裝爲NSValue對象。動畫這個屬性會造成關鍵幀動畫會依次應用轉換矩陣。

• 對於borderColor屬性,強轉每一個CGColorRef類型爲id類型。

• CGFloat類型需要包裝爲NSNumber對象。

• 動畫圖層的contents屬性,指定一組CGImageRef數組。

對於CGPoint類型,你可以創建一組NSNumber數組或者創建CGPathRef對象來指定路徑。當你指定一組點數組,關鍵幀動畫對象描繪一條線段在每個連續的點之間並跟隨這個路徑。當你指定CGPathRef對象,動畫開始在路徑的開始點並跟隨輪廓,包括在任何曲面。你可以使用閉合和開放的路徑。

指定關鍵幀動畫的時間

關鍵幀動畫的時間和節奏比基本動畫更加複雜而且你還可以使用下面的幾個屬性來控制它:

• calculationMode屬性定義計算動畫時間的算法。這個屬性值會影響其他時間相關屬性的使用。

    • 線性和立方動畫,也就是說,calculationMode屬性被設置爲kCAAnimationLinear或者kCAAnimationCubic;使用給定的時間信息來生成動畫。這些模式給你最大的控制動畫時間。

   • 節奏動畫,也就是說,calculationMode屬性被設置爲kCAAnimationPaced或者kCAAnimationCubicPaced;不會依賴keyTimes或者timingFunctions屬性提供的外部時間。而是時間值被隱式地計算並提供給恆定速度的動畫。

    • 離散動畫,也就是說,calculationMode屬性被設置爲kCAAnimationDiscrete;會造成動畫屬性從一個關鍵幀跳到下一個關鍵幀而且不會在中間插值。這種計算模型使用keyTimes屬性而不會使用timingFunctions屬性。

• keyTimes屬性指定每個關鍵幀值的時間。這個屬性只有在計算模型被設置爲kCAAnimationLinear、kCAAnimationDiscrete、kCAAnimationCubic時使用。它不會使用節奏動畫。

• timingFunctions屬性爲每個關鍵幀片段指定時間曲線。

如果你想要自己處理動畫時間,使用kCAAnimationLinear或kCAAnimationCubic模型和keyTimes以及timingFunctions屬性。keyTimes定義每個關鍵幀值的時間點。所有時間的中間值會被時間函數控制;它允許你應用漸入或者漸出曲線到每個片段。如果你沒有指定時間函數,默認是線性函數。

停止正在運行的顯式動畫

動畫通常是一直運行到結束,但是你可以使用下面的方式來提前停止它:

• 爲了從圖層移除一個動畫對象,調用圖層的removeAnimationForKey:方法來移除你的動畫對象。這個方法使用addAnimation:forKey: 方法的key來標記動畫。這個key你不能設置爲nil。

• 爲了移除圖層的所以動畫,調用圖層的removeAllAnimations方法。這個方法會馬上移除所以正在進行的動畫並使用當前狀態信息來重繪圖層。

注意:你不能直接移除隱式動畫。

當你從圖層移除動畫,Core Animation提供使用當前值重繪圖層來響應。因爲當前值通常是動畫的結束值,這會造成圖層的外觀突然變化。如果你想要圖層保留在動畫的最後一幀,你可以使用展示樹的對象來獲取最終值來設置圖層樹的對象。

多個動畫改變

如果你同時應用多個動畫到圖層,你可以使用CAAnimationGroup對象組合它們。使用這個組合對象並提供一個單獨的配置點來簡化多個動畫對象管理。應用於動畫組的時間和時長會重寫到動畫中。

// 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)[UIColor greenColor].CGColor,
            (id)[UIColor redColor].CGColor, (id)[UIColor blueColor].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 Animation提供檢測動畫開始和結束。這些通知是執行與動畫相關的任何管理任務的好時機。例如,你可以使用開始通知來建立一些相關的狀態信息並使用對應的結束通知來移除這個狀態。

這裏有兩種不同的方式來通知動畫的狀態:

• 使用setCompletionBlock: 方法來爲當前事務添加完成代碼塊。當事務的所有動畫完成,事務執行你的完成代碼塊。

• 向CAAnimation對象提供代理對象並實現animationDidStart: 和animationDidStop:finished: 代理方法。

如果你想要連接2個動畫以便一個結束後另一個就會開始,不要使用動畫通知。而是,使用動畫對象的beginTime屬性來設置開始時間。爲了連接2個動畫,設置開始時間爲另一個動畫的結束時間。

怎樣動畫存儲圖層視圖

如果圖層屬於存儲圖層視圖,建議使用UIKit或者AppKit的基於視圖動畫接口來創建動畫。這裏有直接使用Core Animation接口來動畫圖層,但是怎樣創建這些動畫依賴於目標平臺。

在IOS修改圖層的規則

因爲IOS視圖總是有內部的圖層,UIView的大多數數據都是直接從圖層對象派生來的。結果,你對圖層進行的改變會自動地反映到視圖對象。這種行爲意味着你可以使用Core Animation或者UIView接口來進行改變。

如果你使用Core Animation來初始化動畫,你必須在基於視圖的動畫代碼塊中執行所有Core Animation調用。UIView類默認失效圖層動畫但會在動畫代碼塊中重新激活它。所以任何你在動畫代碼塊外面的改變不會造成動畫。下面展示怎樣隱式改變圖層的不透明度並顯式地放置它。在這個例子中,myNewPosition變量已經在之前計算好並被代碼塊捕獲。2個動畫在同一時間開始但是透明度以默認時間進行而位置動畫以動畫對象指定的時間進行。

[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 v10.8之後,建議你減少使用圖層的層次結構並只使用存儲圖層視圖。在這個版本的圖層的重繪政策會讓你自定義存儲圖層視圖的行爲並還會和單獨使用圖層一樣有相同的性能。

安排圖層到圖層的層次結構

圖層的層次結構在很多方面和視圖的層次結構相似。你嵌入一個圖層到另外一個來構建父子關係。(嵌入的圖層爲子圖層,被嵌入的圖層爲父圖層)。父子關係會影響子圖層的各方面。例如,它的內容會放在父圖層的上面,它的位置會相對於父圖層的座標系統而且父圖層的轉換也會影響子圖層。

添加、插入和移除子圖層

當你處理你創建的圖層對象,你可以使用上面的方法。你不能使用這些方法來安排存儲圖層視圖的圖層對象。然而,存儲圖層視圖可以作爲你創建的圖層對象的父圖層。

設置子圖層的位置和大小

當添加和插入子圖層,在圖層出現在屏幕前,你必須設置圖層的位置和大小。你可以在添加子圖層到圖層的層級結構後修改大小的位置,但你應該在圖層創建時設置這些值。

你使用bounds屬性來設置子圖層的大小和使用position屬性來設置位置。bounds的原點總是爲(0,0),大小爲你設置的大小。position屬性是與錨點相關的;錨點默認是在圖層的中心點。如果你沒有爲這些屬性賦值,Core Animation默認初始化圖層的寬高爲0,位置爲(0,0)。

myLayer.bounds = CGRectMake(0, 0, 100, 100);
myLayer.position = CGPointMake(200, 200);

注意:總是設置圖層的寬高爲整數值。

圖層的層次結構是怎樣影響動畫

一些父圖層的屬性可能影響子圖層的動畫行爲。其中一個屬性就是speed屬性,它是動畫的速度的倍增器(multiplier)。這個屬性的值默認爲1.0,但改變成2.0會造成動畫以2倍的速度進行。這個屬性不僅影響被設置的圖層,還有它的子圖層。這種改變也是乘積的。如果子圖層和父圖層都設置速度爲2.0,子圖層的動畫會以4倍速度執行。

大多數圖層對子圖層的影響都在預料的方式進行。例如,應用旋轉轉換到圖層會造成圖層和子圖層都會旋轉。相似地,改變圖層的不透明屬性也會改變子圖層的不透明屬性。

調整圖層的層次結構佈局

Core Animation支持調整子圖層的大小和位置來響應父圖層變化的選項。在IOS,普遍使用存儲圖層視圖使得創建圖層的層次結構不那麼重要;只有手動的佈局更新被支持。對於OS X,其他幾個可用選項使得管理圖層的層次結構更加容易。

圖層級別的佈局只在你使用獨立的圖層對象來構建圖層的層次結構時相關。如果你的圖層都與視圖相關,使用基於視圖的佈局支持更新視圖的位置和大小來響應變化。

使用約束來管理圖層的層次結構在OS X

約束讓你使用一組詳細的圖層和父圖層以及兄弟圖層的關係來指定圖層的位置和大小。定義約束需要下面的步驟:

1.創建一個或者多個CAConstraint對象。使用這些對象來定義約束參數。

2.添加你的約束對象到圖層;圖層的屬性會被約束脩改。

3.獲取共享的CAConstraintLayoutManager對象並賦值給直屬父圖層。

下面展示的屬性用來定義約束並影響圖層的方面。通過基於它的中點邊緣相對於另一圖層的位置,你可以使用約束來改變圖層的位置。你也可以使用約束來改變圖層的大小和位置。你所做的改變可以和父圖層或者其他圖層成比例。你甚至可以添加縮放因子或者約束來形成變化。這種額外的靈活性使得使用簡單的規則來精確地控制圖層的大小和位置成爲可能。


每個約束對象封裝了使用同一個座標軸的2個圖層的幾何關係。2個約束對象的最大值可能賦值給每個座標軸而且這2個約束決定哪個屬性是可變的。例如,如果你指定圖層的左邊和右邊的約束;圖層的大小就會改變。如果你指定圖層的左邊和寬度約束;圖層的右邊的位置就會改變。如果你指定圖層的單獨一個邊的約束,Core Animation會創建一個隱式的約束來保持圖層的大小並適應給定的尺寸。

當你創建約束,你必須總是指定3個信息:

• 圖層的方面。

• 圖層的引用。

• 引用圖層的方面比較。

下面展示簡單的約束來固定圖層和父圖層的垂直中點。當引用父圖層,使用字符串"superlayer"。這個字符串會保留父圖層的引用。使用這種方式消除了需要擁有這個圖層的指針和名稱。它也允許你改變父圖層和讓約束自動應用到父圖層。(當你創建相對兄弟圖層的約束,你必須使用name屬性來指定兄弟圖層的名稱。)

[myLayer addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMidY
                                                 relativeTo:@"superlayer"
                                                  attribute:kCAConstraintMidY]];

爲了在運行時使用約束,你必須附加共享的CAConstraintLayoutManager對象到直屬父圖層。每個圖層都會管理自己子圖層的佈局。賦值這個佈局管理者到父圖層是告訴Core Animation應用子圖層定義的約束。佈局管理者對象自動應用這些約束。在賦值管理者給父圖層後,你不需要去告訴Core Animation更新約束。

爲了查看約束在特定場景是怎樣工作的,考慮下面圖片。在這個例子中,需要layerA的寬高不改變而且在父圖層的中心點。另外,layerB的寬必須匹配layerA,layerB的頂邊必須低於layerA底邊10個點,layerB的底邊必須高於父圖層底邊10個點。


// Create and set a constraint layout manager for the parent layer.
theLayer.layoutManager=[CAConstraintLayoutManager layoutManager];
 
// Create the first sublayer.
CALayer *layerA = [CALayer layer];
layerA.name = @"layerA";
layerA.bounds = CGRectMake(0.0,0.0,100.0,25.0);
layerA.borderWidth = 2.0;
 
// Keep layerA centered by pinning its midpoint to its parent's midpoint.
[layerA addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMidY
                                                 relativeTo:@"superlayer"
                                                  attribute:kCAConstraintMidY]];
[layerA addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMidX
                                                 relativeTo:@"superlayer"
                                                  attribute:kCAConstraintMidX]];
[theLayer addSublayer:layerA];
 
// Create the second sublayer
CALayer *layerB = [CALayer layer];
layerB.name = @"layerB";
layerB.borderWidth = 2.0;
 
// Make the width of layerB match the width of layerA.
[layerB addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintWidth
                                                 relativeTo:@"layerA"
                                                  attribute:kCAConstraintWidth]];
 
// Make the horizontal midpoint of layerB match that of layerA
[layerB addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMidX
                                                 relativeTo:@"layerA"
                                                  attribute:kCAConstraintMidX]];
 
// Position the top edge of layerB 10 points from the bottom edge of layerA.
[layerB addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMaxY
                                                 relativeTo:@"layerA"
                                                  attribute:kCAConstraintMinY
                                                     offset:-10.0]];
 
// Position the bottom edge of layerB 10 points
//  from the bottom edge of the parent layer.
[layerB addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMinY
                                                 relativeTo:@"superlayer"
                                                  attribute:kCAConstraintMinY
                                                     offset:+10.0]];
 
[theLayer addSublayer:layerB];

在上面的例子中,layerB沒有明確設置大小。因爲已定義的約束,layerB的寬高會在佈局更新時自動設置。因此,設置邊框的大小是不必要的。

警告:當創建約束,不要在你的約束中創建循環引用。循環約束是不可能計算約束信息的。當遇到這樣的循環引用,佈局行爲是不確定的。

建立自動佈局規則

自動佈局規則是OS X中另外一種調整大小和位置的方式。在自動佈局規則,圖層的邊是固定還是相對父圖層邊變化的。你可以相似地設計圖層的寬高是固定還是可變的。這種關係通常是在父子圖層中。你不能使用自動佈局規則在兄弟圖層。

爲了建立圖層的自動佈局規則,你必須設置適當的常量到圖層的autoresizingMask屬性。默認的,圖層被設置爲有固定的寬高。在佈局中,圖層的精確的大小和位置被Core Animation自動地計算;這涉及一組基於多個因素的複雜計算。在詢問你的代理對象做任何手動佈局更新前,Core Animation會應用自動佈局行爲,所以你可以在代理中調整自動佈局結果。

手動佈局圖層的層級結構

在IOS和OS X,你可以在父圖層的代理對象中實現layoutSublayersOfLayer: 方法來手動處理佈局。你使用這個方法來調整任何子圖層的大小和位置。當進行手動佈局更新,你需要執行必要的計算每個圖層的位置。

如果你使用的自定義子類,你的子類可以重寫layoutSublayers方法並使用這個方法(而不是代理方法)來處理任何佈局任務。你應該只在需要完整地控制子圖層位置時,重寫這個方法。替換默認實現會阻止Core Animation應用約束或者自動佈局規則在OS X。

子圖層和裁剪

不像視圖,父圖層不會自動裁剪子圖層內容;這些內容會放在邊框矩形之外。而是,父圖層默認允許子圖層展示它們的全部內容。但是,你可以提供設置masksToBounds屬性爲YES來重新激活裁剪。

圖層的裁剪遮蓋(mask)的形狀包括圖層的圓角。下面展示一個圖層來論述masksToBounds屬性是怎樣影響帶有圓角的圖層。當這個屬性設置爲NO,子圖層會展示全部內容,即使它超出父圖層的邊界。改變這個屬性爲YES會造成它們的內容被裁剪。


圖層之間轉換座標值

偶爾,你可能需要轉換一個圖層的座標值到同一屏幕的不同圖層的座標值。CALayer類提供提供一組簡單的轉換方法來達到這一目的:

• convertPoint:fromLayer:

• convertPoint:toLayer:

• convertRect:fromLayer:

• convertRect:toLayer:

除了轉換點和矩形值,你還可以使用convertTime:fromLayer: 和convertTime:toLayer: 方法來轉換圖層之間的時間值。每個圖層都定義了自己的本地時間空間並使用這個時間空間來與系統的其餘部分同步動畫的開始和結束。這些時間空間默認被同步;然而,如果你改變動畫的速度,相應的時間空間也會改變。你可以使用這些時間轉換方法來分析這些問題並保證2個圖層的時間都同步。

進階的動畫技巧

過渡動畫支持圖層的可視變化

正如名字所暗示的,過渡動畫對象創建圖層的動畫視覺過渡。大多數使用過渡對象動畫顯示一個圖層並消失另一個圖層。不像基於屬性的動畫,當動畫改變圖層的一個屬性,過渡動畫會操作圖層的緩存圖像並創建可視效果;這個效果通過單獨改變屬性是很困難或者不可能實現。過渡的標準類型允許你實現透露(reveal)、推進(push)、移動(move)或者淡入淡出(crossfade)動畫。在OS X,你可以使用Core Image的過濾器來創建過渡;使用另一效果類型,例如,擦除(wipes)、翻頁(page curls)、漣漪(ripples)或者你設計的自定義效果。

爲了實現過渡動畫,你創建CATransition對象並添加到圖層。你使用這個過渡動畫並指定過渡類型和開始點,結束點。你不必使用整個過渡動畫。過渡動畫允許你指定開始和結束進程值。這些值讓你可以在動畫中途開始或者結束。

下面展示創建2個視圖的推進(push)過渡動畫。在這個例子中,myView1和myView2都在相同父視圖的相同位置,但是隻有myView1是可視的。這個推進過渡會造成myView1滑出到左邊並淡出直到隱藏,同時myView2從右邊滑進並可見。同時更新2個視圖的隱藏屬性確保在動畫結束後,2個視圖的可見是正確的。

CATransition* transition = [CATransition animation];
transition.startProgress = 0;
transition.endProgress = 1.0;
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromRight;
transition.duration = 1.0;
 
// Add the transition animation to both layers
[myView1.layer addAnimation:transition forKey:@"transition"];
[myView2.layer addAnimation:transition forKey:@"transition"];
 
// Finally, change the visibility of the layers.
myView1.hidden = YES;
myView2.hidden = NO;

當2個圖層都涉及相同過渡,你可以使用同一個過渡對象。使用相同的過渡對象會簡化你的代碼。然而,你可以使用不同的過渡對象並在對每個圖層的過渡參數不同時才這樣做。

下面展示在OS X,怎樣使用Core Image過濾器來實現過渡效果。在你設置過濾器的參數後,賦值過渡對象的filter屬性。之後,應用動畫的過程和其他類型的動畫對象相同。

// 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過濾器到動畫,棘手的是設置過濾器。例如,條形滑動過渡(bar swipe transition),指定輸入角度爲太高或者太低會造成看起來沒有過渡發生。如果你沒有看到你期望的動畫,嘗試調整過濾器的參數爲不同的值。

自定義動畫的時間設置

時間設置(Timing)是動畫重要的一部分,在Core Animation,你可以通過CAMediaTiming協議的方法和屬性爲動畫設置精確的時間設置信息。有2個Core Animation類符合這個協議。CAAnimation類符合這個協議,以便你可以設置時間設置信息到你的動畫對象。CALayer也實現這個協議,以便你能爲隱式動畫設置一些時間相關的功能,雖然包裝了浙西動畫的隱式過渡對象通常提供默認的時間設置信息。

當考慮時間設置和動畫,這是很重要的明白圖層是怎樣和時間設置工作的。每個圖層都有自己的本地時間;這個時間用來管理動畫的時間設置。通常,2個不同圖層的本地時間是足夠接近的;你可以爲每個圖層指定相同時間值而且用戶不會注意到。然而,通過父圖層或者自己的時間設置參數,圖層的本地時間可以被修改。例如,改變圖層的speed屬性會造成動畫的持續時間按比例地變化。

爲了幫助你確保圖層的時間值是適合的,CALayer類定義convertTime:fromLayer: 和 convertTime:toLayer: 方法。你可以使用這些方法轉換固定時間到圖層的本地時間或者轉換圖層的時間值到另一個圖層。這些方法考慮到媒體時間屬性(可能影響圖層的本地時間)並返回你想要使用到另一個圖層的值。下面的例子展示你應該定期地獲取圖層的本地時間。CACurrentMediaTime函數是一個返回計算機當前時鐘時間的便利函數,這個時間會被獲取並轉換爲圖層的本地時間。

CFTimeInterval localLayerTime = [myLayer convertTime:CACurrentMediaTime() fromLayer:nil];

一旦你獲取圖層的本地時間值,你可以使用這個值來更新動畫對象的時間相關屬性或者圖層。你還可以實現一些有趣的動畫行爲:

• 使用beginTime屬性來設置動畫的開始時間。通常,動畫會在下一個更新循環開始。你可以使用beginTime參數開延時動畫開始時間機秒鐘。連接2個動畫的方式是設置動畫的開始時間來匹配另一個動畫的結束時間。

如果你延時動畫的開始時間,你可能想要設置fillMode屬性爲kCAFillModeBackwards。這個fillMode會造成圖層展示動畫的開始值,即使圖層樹的圖層對象包含不同的值。沒有這個fillMode,在動畫執行前,你可能會立即看到最終值。其他fillMode也是可用的。

• autoreverses屬性造成動畫執行一段持續時間之後,返回動畫的開始值。這個屬性可以和repeatCount屬性結合來實現動畫返回並在開始和結束值來回出現。爲autoreversing動畫設置repeatCount爲整數值(例如,1.0)會造成動畫停止到開始值。添加額外的一半(例如,repeatCount爲1.5)會造成動畫停止在結束值。

• 組動畫(group animations)使用timeOffset屬性來實現動畫之間的延時啓動。

暫停和恢復動畫

爲了暫停動畫,你可以利用圖層符合CAMediaTiming協議並設置圖層動畫的speed屬性爲0.0。設置speed爲0會暫停動畫直到你改變這個值爲非0。

-(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類方法。在這2個調用之間是你所需要的事務改變。例如,爲了改變圖層的2個屬性,你可以使用下面的代碼:

[CATransaction begin];
theLayer.zPosition=200.0;
theLayer.opacity=0.0;
[CATransaction commit];

其中一個使用事務主要原因是能在顯式事務的範圍內進行持續時間、時間函數和其他參數的改變。你可以賦值完成代碼塊到完整的事務以便你能夠獲取一組動畫完成的通知。改變動畫參數需要通過使用setValue:forKey: 方法來適當修改事務字典的鍵值對。例如,爲了改變默認的持續時間爲10秒,你可以改變kCATransactionAnimationDuration鍵的值。

[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat:10.0f]
                 forKey:kCATransactionAnimationDuration];
// Perform the animations
[CATransaction commit];

您可以在需要爲不同的動畫集提供不同的默認值的情況下嵌套事務。爲了嵌套事務在另一個事務內,只是再次調用begin類方法。每次begin調用必須有匹配的commit方法調用。只有在你提交改變到最外圖層的事務,Core Animation開始關聯的動畫。

[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
添加透視圖到你的動畫

APP能在3維空間操作圖層,但是爲了簡化,Core Animation使用平行投影來展示圖層,從本質上講,將場景變平爲二維平面。這種默認行爲造成相同大小的圖層會有不同的zPosition值,但看起來是相同大小,即使它們在z軸很遠。你通常會在三維空間中看到這樣一個場景的視角已經消失了。但是,你可以通過修改圖層的轉換矩陣幷包含視角信息來改變這種行爲。

當修改場景的視角,你需要修改父圖層的sublayerTransform矩陣,它包含被看見的圖層。通過應用相同的視角信息到所有子圖層來簡化你的代碼。這確保應用到兄弟子圖層的視角信息是正確的,這些子圖層在不同的平面上相互重疊。

下面展示爲父圖層創建簡單的視角轉換。在這個例子,自定義eyePosition變量指定從看的圖層到z軸的相對距離。通常,你指定eyePosition爲正值來保持圖層的方向。更大的值會導致更平坦的場景,而較小的值則會導致圖層之間更大的視覺差異。

CATransform3D perspective = CATransform3DIdentity;
perspective.m34 = -1.0/eyePosition;
 
// Apply the transform to a parent layer.
myParentLayer.sublayerTransform = perspective;

父圖層設置後,你可以改變zPosition屬性在任何子圖層並觀察它們的大小是怎麼基於從eye position的相對距離變化的。

改變圖層的默認行爲

Core Animation使用動作對象來實現圖層的隱式動畫行爲。動作對象是實現CAAction協議並定義一些層的先關行爲。所以CAAnimation對象都實現這個協議,當圖層的屬性被改變,這些對象通常被賦值並執行。

動畫屬性是其中一中動作,你可以自己定義動作對象並關聯到圖層對象。

自定義符合CAAction協議的動作對象

爲了創建自己的動作對象,符合CAAction協議並實現runActionForKey:object:arguments: 方法。在這個方法,使用這些參數信息來執行你想要在圖層對象的行爲。你可以使用這個方法來添加動畫到圖層或者做其他任務。

當你定義動作對象,你必須定義動作對象要怎樣被觸發。動作的觸發器定義一個註冊圖層鍵。動作對象可以被下面一些情況觸發:

• 圖層的屬性被改變。這可以是圖層的任何屬性而且不只是動畫那部分。(你可以爲自定義屬性關聯動作)標識動作的鍵是屬性的名稱。

• 圖層位於可視狀態或者被添加到圖層的層次結構。標識這個動作的鍵爲kCAOnOrderIn。

• 圖層從層次結構移除。標識這個動作的鍵爲kCAOnOrderOut。

• 圖層即將執行過渡動畫。標識這個動作的鍵爲kCATransition。

動作對象必須安裝到圖層纔會有效

在動作能被執行前,圖層需要發現對應的動作對象執行。圖層先關的鍵是屬性名或者是標識動作的特殊的字符串。當適當的時間發生到圖層,圖層會調用actionForKey: 方法來搜索關聯鍵的動作對象。在搜索的時候插入動作對象併爲這個鍵提供相關的動作對象。

Core Animation會以下面的順序尋找動作對象:

1. 如果圖層有代理對象並且代理實現actionForLayer:forKey: 方法,圖層會調用這個方法。代理必須實現下面任務:

    • 爲這個鍵返回動作對象。

    • 如果不能處理這個動作,返回nil,這回造成搜索繼續。

    • 返回NSNull對象,這回造成搜索馬上結束。

2. 圖層在它的actions字典尋找對應鍵的動作對象。

3. 圖層尋找style字典對應鍵的動作對象。(換句話說,style字典包含actions的鍵並且值也是一個字典。圖層會在第二個字典尋找指定鍵的動作對象) 

4. 圖層調用defaultActionForKey: 類方法。

5. 圖層執行Core Animation的定義的隱式動作。

如果你在搜索的時候提供動作對象,圖層會停止搜索並執行返回的動作對象。當發現動作對象,圖層調用動作對象的runActionForKey:object:arguments: 方法來執行動作。如果你已經在CAAnimation定義的鍵再定義動作對象,你可以使用這個方法的默認實現來執行動作。如果你自定義CAAction協議的動作對象,你必須自己實現這個方法。

在哪裏安裝動作對象,依賴於需要怎樣修改圖層。

• 對於只在特殊的情況下才應用的動作對象或者對於已經使用代理對象的圖層,提供代理對象並實現actionForLayer:forKey: 方法。

• 對於不使用代理對象的圖層對象,直接添加動作到層的actions字典。

• 對於和圖層對象的自定義屬性相關的動作對象,包含這個動作到style字典。

• 對於圖層的基本行爲動作,子類這個圖層對象並重寫defaultActionForKey: 方法。

下面展示這個代理方法的實現。在這個例子中,代理尋找圖層的contents屬性變化並在替換新的內容時,使用一個過渡動畫。

- (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屬性爲ture來失效隱式動畫。
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue
                 forKey:kCATransactionDisableActions];
[aLayer removeFromSuperlayer];
[CATransaction commit];

提高動畫性能

Core Animation是一個很好的方式來提高APP動畫的幀率,但是它不保證能提高性能。特別在OS X,你必須選擇最有效的方式來使用Core Animation行爲。和所有與性能相關的問題,你應該使用Instruments來測量和跟蹤APP的性能以便確保性能時提高的而不是下降的。

爲你的OS X視圖選擇最後的重繪政策

NSView的默認重繪政策保存原始的繪畫行爲,即使視圖是存儲圖層視圖。如果你正在使用存儲圖層視圖,你應該解釋重繪政策選擇和選擇這個政策會怎樣提高性能。在大多數情況下,默認政策並不是總能提高最佳性能。NSViewLayerContentsRedrawOnSetNeedsDisplay政策很可能會減少APP描繪任務並提高性能。其他政策也可能會提供更好哦性能在特定的視圖。

常用的提示和技巧
儘可能使用不透明圖層

設置圖層的opaque屬性爲YES,讓Core Animation知道不需要維持圖層的透明度信號。沒有透明度信號意味着編譯器不需要把圖層的內容和背景內容混合,這會節省渲染的時間。然而,這個屬性對圖層是相對重要的(一部分存儲圖層視圖或者Core Animation創建的底層圖層位圖)。如果你直接賦值圖形到圖層的contents屬性,不管opaque屬性的值,圖形的透明度信號都會被保存。

CAShapeLayer對象儘量使用簡單路徑

CAShapeLayer類提供渲染路徑來創建內容並在編譯時生成位圖圖形。優點是圖層總是在儘可能最佳的分辨率下描繪路徑,但是這個優點也會話費額外的渲染時間。如果你提供的路徑是複雜,光柵化這個路徑可能會很昂貴。如果圖層的大小經常變化(必須經常重繪),花費的繪畫時間會增加併成爲性能瓶頸。

減少形狀圖層的繪畫時間是劃分複雜形狀爲簡單形狀。使用簡單的路徑並使用多個CAShapeLayer對象來組合佈局;這會比描繪一個複雜大路徑快得多。因爲描繪操作發生在CPU然而組合任務發生在GPU。在這種簡單化的方式下,潛在的性能獲取依賴你的內容。在優化前必須測量你的代碼性能以便有個比較。

顯式地設置相同圖層的內容

如果你在多個圖層對象使用相同圖形,加載圖形並直接賦值到圖層的contents屬性。賦值圖形到contents屬性阻止圖層分配內存存儲。圖層會使用這個圖形來提供存儲。當多個圖層使用相同圖形,這意味着所有這些圖層共享相同內存而不是創建圖形的副本。

總是設置層的大小爲整數值

爲了最好的結果,總是設置層對象的寬高爲整數值。雖然圖層邊框寬高爲浮點型,圖層邊框最終被用來創建位圖圖形。指定寬高爲整數值可以簡化Core Animation的工作。

圖層儘可能使用異步渲染

drawLayer:inContext: 代理方法或者視圖的drawRect: 方法通常在主線程同步進行的。在一些情況下,同步地描繪內容不會提供最好的性能。如果你意識到你的動畫性能不好,你可以激活drawsAsynchronously屬性並讓這些描繪操作在後臺進行。如果你這樣做,確保你的描繪代碼是線程安全。

當添加陰影到你的圖層,指定陰影路徑

讓Core Animation確定陰影的形狀可能會很昂貴並影響APP性能。使用CALayer的shadowPath屬性來明確地指定陰影形狀。當你爲這個屬性指定路徑對象,Core Animation使用這個形狀來描繪並緩存陰影效果。對於那些形狀不會改變或者很少改變的圖層,這會極大地提高性能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章