Core Animation 的使用離不開 Layer 對象。Layer 管理着 app 可見的內容,爲視覺內容和現實效果提供修改的選項。儘管在 iOS 中默認啓用了 layer 功能,然而在 OS X 中要想得到 layer 帶來的性能提示就只能手動地啓用它。一旦 layer 被啓用,就能夠盡情的經過配置和操做 app 的 layer 來多的想要的效果了。html
在 iOS 的 app 當中。Core Animation 老是處於啓用狀態,每個視圖都是構建在 layer 之上。而 OS X 就須要按照如下步驟完成 COre Animation 的啓用:ios
在前面的 layer 啓用方法中,建立的都是 layer-backed 視圖。在 layer-backed 視圖中,系統會負責建立下面的 layer 對象並監管 layer 對象的更新。在 OS X 中,你能夠經過建立 layer-hosting 視圖手動建立和管理底層的 layer 對象(在 iOS 中沒法建立 layer-hosting 視圖)。關於建立 layer-hosting 視圖,詳見 Layer Hosting Lets You Change the Layer Object in OS X。緩存
Layer-backed 視圖默認建立一個 CALayer 類的實例,並且多數時候你並不須要其餘類型的 layer 對象。Core Animation 提供了不一樣的 layer 類型,每一個類型都提供你可能須要的具體的功能,正確地選擇一個不一樣的 layer 類型能夠用簡單的方式幫你提高性能,例如,CATiledLayer 類能夠用高效的方式優化大圖片的現實。架構
你能夠在 iOS 視圖中重寫 layerClass 方法改變默認的 layer 類型。大部分 iOS 視圖建立 CALayer 對象存儲它的內容。對於你的視圖這個默認選擇是有益的,你不須要改變它。可是在特定的狀況下你能夠找到更合適的 layer 類型。例如,你可能在如下狀況中想改變 layer 類型:app
改變視圖的 layer 類型很簡單,代碼 2-1 是一個實例。你須要作的只是重寫 layerClass 方法,返回你想替換的類型。在現實以前,視圖會調用這個方法,使用返回的類型建立一個新的 layer 對象,一旦建立,就不能在更改。框架
代碼 2-1 Specifying the layer class of an iOS viewiview
+ (Class) layerClass{ return [CAEAGLLayer class]; }
關於更多的 layer 類型以及使用,詳見 Different Layer Classes Provide Specialized Behaviors。ide
在 NSView 對象中能夠重寫 makeBackingLayer 方法實現默認 layer 類型的變動。在這個方法的實現裏,建立並返回一個你想爲視圖定義的 layer 對象。你可能會在諸如使用滾動和磁鐵 layer 的狀況下重寫。性能
更多 layer 類型及使用,詳見 Different Layer Classes Provide Specialized Behaviors。優化
一個 layer-hosting 視圖是一個 NSView 對象,下面的 layer 對象須要你本身手動建立和管理。也許在想控制視圖相關類型的狀況下你纔會使用 layer hosting。例如,你建立一個 layer-hosting 視圖以便用一個自定義的 layer 類型來替換默認的 CALayer 類型。也可能會在用單個視圖管理不相關 layer 組成層次。
當你調用視圖的 setLayer: 方法並提供一個 layer 對象時,AppKit 爲這個 layer 提供了方便的操做。一般 AppKit 更新視圖的 layer 對象,可是在 layer-hosting 這種狀況下它並不爲大多數的屬性提供更新。
代碼 2-2 展現了怎麼建立一個 layer-hosting 視圖和自定義的 layer 對象,並在屏幕顯示前關聯二者。另外爲了設置layer 對象,你仍需調用 setWantsLayer: 方法以便讓視圖知道它應該使用 layer:
代碼 2-2 Creating a layer-hosting view
// Create myView... [myView setWantsLayer:YES]; CATiledLayer *hostedLayer = [CaTiledLayer layer]; [myView setLayer:hostedLayer]; // Add myView to a view hierachy.
若是你選擇了 host layer,你必須設置 contentScale 屬性以即可以顯示高的分辨率。關於高分辨率的內容和縮放,詳見 Working with High-Resolution Images。
Core Animation 定義了許多標準的 layer 類型,每種類型都有特定的用處。CALayer 是這些 layer 對象的根類,它定義了全部 layer 對象都應該支持的行爲,是 layer-backed 視圖的默認類型。在表 2-1 中展現了可用的 layer 類型:
表 2-1 CALayer subclasses and their uses
Class | Usage |
---|---|
CAEmitterLayer | 用來實現基於 Core Animation 的例子發射系統。發射 layer 對象控制 粒子的原點和生成。 |
CAGradientLayer | 用來繪製被彩色的坡度填充的 layer(具備圓角邊界)。 |
CAEAGLLayer/CAOpenGLLayer | 用來在使用 OpenGL ES(iOS) 或 OpenGL(OS X)時存儲須要被繪製的上下文。 |
CAPreplicatorLayer | 當你想自動地複製一個或更多的子 layer 時,此對象能夠爲你複製,並讓你改變複製的屬性。 |
CAScrollLayer | 用來管理由多個子 layer 構成的大的滾動區域。 |
CAShapeLayer | 能夠繪製貝塞爾曲線,能夠很方便地繪製基於路徑的曲線圖形。可是,曲線結果的渲染是在主線程上完成和緩存的。 |
CATextLayer | 用來渲染平面或帶有屬性的文本字符串。 |
CATiledLayer | 用來管理能夠單獨渲染或一片片渲染的大圖片。 |
CATransformLayer | 用來渲染真是的 3D layer 層次,而不是像其餘 layer 類型同樣的平面層次。 |
QCCompositionLayer | 用來渲染一個 Quartz COmposer 構造。(僅用於 OS X) |
layer 實際上是管理你 app 內容的數據對象。一個 layer 由一張位圖構成,包含了你想顯示的可視數據。你能夠用一下三種方式爲位圖提供內容:
由於 layer 只是管理位圖的容器,你能夠直接賦給 layer 的 contents 屬性一張圖片。直接賦值一張圖片很簡單並且讓你可以在屏幕上徹底展現你想一想要的內容。layer 會直接使用你提供的圖片而不是建立一個圖片的拷貝。這種行爲能夠在多個使用同一張圖片的狀況下節約內存佔用。
你賦值的圖片類型必須是 CGImageRef 類型。(OS X v10.6 及以後能夠直接使用 NSImage 對象。)同時你也須要注意圖片的分辨路問題,在 retina 顯示的設備上,可能還須要你適配圖片的 contentsScale 屬性。具體的layer高分辨率內容詳見 Working with High-Resolution Images。
若是你的 layer 內容是動態變化,可使用委託對象根據須要提供內容的更新。在顯示時,layer 調用委託方法以提供須要的內容:
委託對象必須實現 displayLayer: 或 drawLayer:inContext: 方法,若是這兩個方法都被實現了,layer 只調用 displayLayer: 方法。
當你的 app 想加載或者建立顯示的位圖時,重寫 displayLayer: 方法是最合適的。代碼 2-3 展現了一個實現 displayLayer: 委託方法的示例。在這個例子中,委託使用一個幫助對象來加載和顯示須要的圖片。委託方法根據內部的狀態選擇顯示哪張圖片,在這個例子中狀態是自定義的屬性 displayYesImage。
代碼 2-3 Setting the layer contents directly
- (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: 方法動態繪製內容。代碼 2-4 展現了實現 drawLayer:inContext: 方法的示例。在這個例子中,用固定的款多和當前渲染的顏色繪製了簡單的弧形路徑。
代碼 2-4 Drawing the contents of a layer
- (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); }
對於自定義內容的 layer-backed 視圖,你應該繼續重寫視圖的方法繪製本身的內容。layer-backed 視圖自動實現委託以及委託方法,你不該該更改它的配置,用 drawRect: 方法繪製本身的內容。
在 OS X v10.8 及以後,另外一個繪製的方法是重寫你的視圖的 wantsUpdateLayer 和 updateLayer 方法提供位圖。重寫 wantsUpdateLayer 並返回 YES 會使 NSView 類型使用另外一種渲染途徑。視圖會調用你的 upateLayer 方法代替調用 drawRect:,在方法體中要直接爲 layer 的contents 屬性賦值一張位圖。這是 AppKit 架構指望你直接設置視圖 layer 內容的地方。
若是你自定義了一個 layer 類,你能夠重寫繪製方法定製本身的內容。layer 對象生成獨有的內容有點特殊,可是它能管理顯示的內容。例如,CATiledLayer 把一張大圖分割成一片片來管理,也能夠單獨顯示一片。當 layer 收到須要顯示哪一片的信息時它纔會直接管理繪製行爲。
在繼承類中,你能夠用如下兩種技術繪製本身的 layer 內容:
重寫哪一個方法取決於你想怎麼控制繪製過程。display 方法是更新 layer 內容的主要入口點,所以重寫此方法能夠徹底控制內容的顯示,同時也意味着你須要在此方法中負責建立 CGImageRef 類型的 contents 屬性值。若是你只想繪製內容或者用 layer 管理繪製操做,你能夠重寫 drawInContext: 方法替代重寫 display,在此方法中 layer 爲你建立後臺存儲。
當你爲 layer 的 contents 屬性賦值一張圖片時,layer 的 contentsGravity 屬性決定了如何操做這張圖片適配當前的界面。若是圖片比當前界面大或者小,默認狀況下 layer 對象會放大/縮小圖片作可用空間的適配。若是 layer 界面的尺寸比和圖片的不一樣會致使圖片扭曲失真。你能夠用 contentsGravity 屬性確保你的內容以最合適的方式展示。
contentsGravity 屬性值分如下兩類:
圖 2-1 展現了 position-based gravity 設置是如何影響你的圖片的。用 kCAGravityCenter 常量,每個均可以把圖片固定在 layer 的界面邊緣或者邊角內。kCAGravityCenter 常量能夠居中放置圖片。這些常量都不會致使圖片縮放失真,所以圖片老是以原始尺寸呈現。若果圖片的尺寸大於 layer 的界面,則會致使圖片的超出部分被剪去,若是圖片較小,圖片就不能遮蓋住 layer 的背景色。
圖 2-1 Position-based gravity constants for layers
圖 2-2 展現了 scaling-based gravity 常量如何影響圖片。若是圖片不適配 layer 界面這些常量會縮放圖片。這些模式的不一樣之處是如何縮放時處理圖片的尺寸比,其中有些會保護尺寸比。默認狀況下,layer 的 contentsGravity 屬性值是 kCAGravityResize 常量,這個常量是惟一不包含圖片尺寸比的。
圖 2-2 Scaling-based gravity constants for layers
layer 並不會根據設備的分辨率而設置自身的分辨率。layer 只簡單地存儲一個指向你的位圖的指針並以最合適的方式顯示有效的像素。若是你爲 layer 的contents 屬性提供一張圖片,你必須設置 layer 的 contentsSale 屬性告訴 Core Animation 這張圖片的分辨率,此屬性的默認值是 1.0,若是在 retina 屏幕上顯示就須要設置爲 2.0。
只有在直接爲 layer 賦值一張位圖時纔會更改 contentsScale 屬性。在 UIKit 和 AppKit 中一個 layer-backed 視圖會自動根據屏幕的分辨路和視圖管理的內容設置 scale factor 爲合適的值。例如,若是你在 OS X 中設置 layer 的contents 屬性爲一張 NSImage 對象,AppKit 會查找這張圖片是否存在普通和高分辨率,若是有都存在,AppKit 會使用正確的一張爲當前分辨率設置 contentsScale 屬性。
在 OS X 中,position-based gravity 常量影響被賦值給 layer 的 NSImage 對象的圖片描述,由於這些常量不會致使圖片被縮放,Core Animation 依賴 contentsScale 屬性用合適的像素密度顯示圖片。
在 OS X 中,layer 的委託對象能夠實現 layer:shouldInheritContentsScale:fromWindow: 方法,用它來響應縮放相關改變。由於窗口在標準和高分辨率的屏幕間移動,不管給定窗口的分辨率什麼時候改變, AppKit 自動調用此方法。若是委託支持 layer 圖片分辨率的更改,你實現的這個方法應該返回 YES 值。而後這個方法應該更新 layer 的 contents 以響應新的分辨率。
layer 對象已經內置了一些視覺修飾,如 border 和 背景色,你能夠用來修飾 layer 的主要內容。由於這些修飾不須要你坐任何渲染,有時能夠把 layer 做爲單獨的實體使用。你所要作的只是設置一下 layer 的屬性,剩下的渲染和動畫都是 layer 處理。layer 其餘的視覺裝飾的效果說明,詳見 Layer Style Property Animations
一個 layer 在他基於圖片的內容中能夠顯示填充的背景色和邊框加粗。背景色的渲染是在 layer 的圖片內容以後,邊框加粗則是在上面,如圖 2-3 。若是 layer 包含子 layer,也會顯示在邊框之下。由於背景色在圖片之下,要想顯示只有設置圖片的透明度。
圖 2-3 Adding a border and background to a layer
代碼 2-5 展現了怎麼設置 layer 的背景色和邊框。這些屬性都是能夠造成動畫的。
myLayer.backgroundColor = [NSColor greenColor].CGColor; myLayer.borderColor = [NSColor blackColor].CGColor; myLayer.borderWidth = 3.0;
注意:你可使用任意類型的顏色設置 layer 的背景色,包括透明色或者模型圖片。當使用模型圖片時,Core Graphics 處理模型圖片的渲染,使用的是標準的座標系統,ios 系統的座標系統不一樣於 OS X。所以,在 iOS 中圖片的渲染默認是自上而下的,除非你翻轉座標。
若是你要設置 layer 的背景色爲不透明色,請考慮設置 layer 的opaque 屬性爲 YES。這樣能夠在屏幕上構築 layer 和消除 layer 的後臺存儲以管理管道的 alpha 值時提升性能。儘管如此,若是 layer 同時又一個非零的圓角就不用設置爲不透明瞭。
你能夠爲 layer 添加圓角效果。一個圓角就是 layer 的四個角呈現想通的弧度,如圖 2-4 。若是不設置 layer 的屬性 masksToBounds 爲 YES ,圓角就不會影響 layer cotents 屬性的圖形內容。然而,圓角老是影響 layer 的背景色和邊框的渲染。
圖 2-4 A corner radius on a layer
爲 layer 的 cornerRadius 指定一個值能夠設定其圓角。你設定的半徑值的單位是 point,應用於四個邊角。
CALayer 類型有許多屬性能夠配置陰影效果。加載layer 上的陰影效果可使其想浮動在內容之上。在你的 app 中你回發現有時這個裝飾效果頗有用。經過 layer,你能夠控制陰影的顏色,放置相關的 layer 屬性如 content,opacity 和 shape。
layer 的陰影不透明度默認是 0,使陰影不顯示。改變不疼名度爲非零值可讓 Core Animation 繪製出陰影。由於陰影默認是直接放在layer 下面的,在你能看到它以前須要先改變陰影的偏離值。有一點須要注意,你爲陰影指定的偏離值使用的是 layer 的本地座標系統,在 iOS 和 OS X 中是不一樣的。圖 2-5 展現了一個偏向於右下方的 layer 陰影。在 iOS中,這須要指定 y 軸值爲正數,而 OS X 中就須要負數了。
圖 2-5 Appling a shadow to a layer
當爲 layer 添加陰影時,陰影成爲 layer 的一部分但實際卻偏離出layer 的邊界框。若是你爲 layer 啓用 masksToBounds 屬性,會產生陰影效果被從邊緣剪去的結果。若是你的 layer 包含透明的內容,會致使layer 下面的陰影正常顯示,可是超出 layer 的則不顯示的奇怪效果。若是你既想要有陰影又想使用邊界遮蓋,可使用兩個 layer 替換一個。把內容放在遮掩邊界的 layer 中,而後把這個 layer 嵌入到另外一個,另個一 layer 能夠實現陰影效果。
關於怎麼在 layer 中應用陰影效果,詳見 Shadow Properties.
在 OS X 的 app 中,你能夠直接爲你的 layer 內容應用 Core Image 濾鏡。你能夠模糊或者銳化 layer 內容,改變顏色,扭曲內容,或者變現其餘類型的操做。例如,一個圖片處理程序能夠非破壞性地使用這些濾鏡修改圖片,視頻編輯程序能夠用他們實現不一樣的過分類型效果。由於這些濾鏡是經過硬件應用在 layer 的內容內,渲染過程很快很平滑。
注意:你不能在 iOS 中爲 layer 對象添加濾鏡。
對於一個給定的 layer,你能夠在layer 的前景和北京內容上應用濾鏡。前景內容包含 layer 自身的全部,如 contents 屬性的圖片,它的背景色,邊界,以及子 layer 的內容。背景內容是 layer 下面的內容,但不是 layer 的實際部分。大多數 layer 的背景內容是父 layer 的即時內容,被 layer 徹底或者部分低遮掩。例如,當你想讓用戶關注 layer 的前景內容時能夠設置背景 模糊濾鏡。
你能夠按照如下的方法爲 layer 添加 CIFilter 對象實現濾鏡:
爲 layer 添加濾鏡,首先須要建立和定位一個 CIFilter 對象,而後再添加進 layer 前配置它。CIFilter 類包含幾個能夠定位到可用 Core Image 濾鏡的類方法,好比 filterWithName: 方法。建立濾鏡只是第一步。許多濾鏡有定義怎麼修改圖片的參數。例如,一個模糊濾鏡須要輸入半徑參數以便決定要應用的模糊數量。做爲濾鏡配置進程的一部分你應當提供這些參數值。有一個參數不須要你明確,就是 layer 自身提供的圖片。
在添加濾鏡到 layer 前,最好先配置好濾鏡的相關參數。之因此這樣是由於一旦添加進去後就不能對 CIFilter 對象作出修改。可是你能夠在加入濾鏡以後經過 layer 的setValue:forKeyPath: 方法改變濾鏡的值。
代碼 2-6 展現瞭如何爲 layer 對象建立和應用一個捏放變形的濾鏡。這個濾鏡捏放的原像素是 layer 內部的,把這些像素儘量的想中心點靠攏。注意,你不須要明確指出輸入的圖片,由於 layer 的圖片被自動使用。
圖 2-6 Applying a filter to a layer
CIFilter* aFilter = [CIFilter filterWithName:@"CIPinchDistortion"];
[aFilter setValue:[NSNumber numberWithFloat:500.0] forKey:@"inputRadius"];
[aFilter setValue:[NSNumber numberWithFloat:1.25] forKey:@"inputScale"];
[aFilter setValue:[CIVector vectorWithX:25.0 Y:150.0] forKey:@"inputCenter"];
myLater.filters = [NSArray arrayWithObject:aFilter];
關於 Core Image 濾鏡的更多信息,詳見 Core Image Filter Reference。
在 OS X 中,layer-backed 視圖爲何時候決定更新 layer 的內容提供了幾種不一樣的策略。由於本地 AppKit 繪製模型和介紹過的Core Animation 存在差別,這些策略可使你的舊代碼向 Core Animaiton 遷移更容易。你能夠在每個視圖中配置這些策略確保獲得最好的性能。
每個視圖定義一個 layerContentsRedrawPolicy 方法,此方法返回視圖 layer 的重繪策略。你能夠經過 setLayerContentsRedrawPolicy: 方法設置策略。爲了保護和傳統繪製模型的兼容性, AppKit 經過 NSViewLayerContentsRedrawDuringViewResize 修改默認的重繪策略。可是,你能夠修改策略爲表 2-2 中的任意值。注意,推薦的重繪策略並非默認的。
表 2-2 Layer redraw policies for OS X views
Policy | Usage |
---|---|
NSViewLayerContentsRedrawOnSetNeedsDisplay | 這是推薦的方針,使用此方針可使視圖幾何改變時不會自動致使視圖更新 layer 內容。layer 存在的內容被拉伸和操做,以便加快幾何的改變。爲了能使視圖重繪更新 layer 的內容,你必須明確地調用視圖的 setNeedsDidsplay: 方法。 此方針比較表明 Core Animation layer 的標準行爲。然而,這並非默認的方針並且須要明確地設定。 |
NSViewLayerContentsRedrawDuringViewResize | 這是默認的重繪方針。不管視圖幾何何時改變,此方針依靠從新緩存 layer 的內容維護和 傳統的 AppKit 繪製的最大兼容性。在尺寸操做的過程當中,你的 app 主線程會調用視圖的 drawRect: 方法屢次,每次結束時產生此行爲。 |
NSViewLayerContentsRedrawBeforeViewResize | AppKit 在位圖的尺寸操做和緩存前繪製 layer 的最終尺寸。從新的尺寸操做使用的是已經緩存的位圖做爲開始的圖片,縮放它以便適應就得邊框。它最終以動畫的形式表現出位圖的最終尺寸。這種行爲在動畫的開始使視圖的內容表現爲拉伸或失真好過於初始的表現不重要或者不知道關注。 |
NSViewLayerContentsRedrawNever | 使用此方針,即便你調用 setNeedsDisplay: 方法 AppKit 也不會更新 layer。對於那些內容不改變並且尺寸不怎麼變化的視圖此方針是最合適的。例如,你可能用於固定尺寸的內容或者背景元素。 |
視圖的重繪方針緩解了單獨子 layer 的頻繁使用,提高了繪製的性能。先前介紹的視圖重繪方針,有幾個會使 layer-backed 的視圖繪製的頻繁度高於實際須要。根據 OS X v10.6 繪製方針的介紹,強烈推薦你設置 layer-backed 視圖的重繪方針,而不是大量的使用 layer 層次構建。
CAAnimation 和 CALayer 類支持經過 key-value 編碼擴展自定義的屬性。你可使用這個行爲爲 layer 添加數據,並用自定義的 key 來檢索。你甚至能夠用自定義的屬性關聯 actions,以便那你在改變屬性時產生響應的動畫。
關於如何設置自定義屬性,詳見 Key-Value Coding Compliant Container Classes。關於爲 layer 對象添加 actions 的信息,詳見 Changing a Layer's Default Behavior。
在打印期間,layer 按須要重繪他們的內容爲打印環境提供便利。因爲 Core Animation 通常依賴緩存位圖在屏幕上渲染,打印的時候會重繪這些內容。須要特別提出,若是 layer-backed 視圖使用 drawRect: 方法提供 layer 的內容,Core Animation 在爲了生成打印的 layer 內容的打印期間會再次調用 drawRect: 方法。