Layer對(duì)象的設(shè)置

周末第二彈, 繼續(xù)Core Animation的基礎(chǔ)內(nèi)容復(fù)習(xí).

Layer對(duì)象是Core Animation中的核心概念. layer管理著app中的可視化內(nèi)容, 并且為你提供了改變這些內(nèi)容樣式的選擇. iOS app是自動(dòng)支持layer的, OS X的開發(fā)者們則需要手動(dòng)去設(shè)置是否支持layer(默認(rèn)是不支持的). 一旦開始支持layer, 那么你就需要理解如何配置和操作app中的各個(gè)layer以此來(lái)達(dá)到你想得到的效果.

App中支持Core Animation

在iOS app中, Core Animation是自動(dòng)支持的, 并且每一個(gè)view都自帶一個(gè)相對(duì)應(yīng)的layer. 在OS X中, 想要使用Core Animation則需要手動(dòng)去設(shè)置, 步驟如下:

  • 添加QuartzCore框架.
  • 想要NSView對(duì)象支持layer需要做以下幾步:
    • 在nib文件中, 在View Effects inspector中勾選想要支持layer的view.

    • 如果是用代碼創(chuàng)建的view, 調(diào)用view的setWantsLayer:方法, 傳入YES來(lái)指定view會(huì)使用layer.

上面的任何一種方法都會(huì)創(chuàng)建一個(gè)基于layer的view. 之后系統(tǒng)會(huì)自動(dòng)創(chuàng)建layer對(duì)象, 并且保持其更新.

改變View對(duì)應(yīng)的layer對(duì)象

默認(rèn)情況下, 基于layer的view已經(jīng)創(chuàng)建了一個(gè)CALayer類的實(shí)例, 并且在絕大多數(shù)情況下, 你可能并不需要改變已經(jīng)創(chuàng)建好的這個(gè)layer實(shí)例對(duì)象. 然而, Core Animation是提供了不同的layer類供你選擇的, 每種layer類都有自己特殊的能力, 如果你用的到, 你可能就會(huì)改變默認(rèn)情況下創(chuàng)建出來(lái)的layer類. 選擇不同的layer類可以讓你以一種簡(jiǎn)單的方式提高內(nèi)容的展示效果或是支持特定類型的內(nèi)容展示. 比如CATiledLayer類就能夠以一種更有效的方式來(lái)展示大圖.

使用UIView來(lái)改變layer所屬的類

你可以通過(guò)重寫view的layerClass方法, 返回一個(gè)不同的類對(duì)象以此來(lái)改變layer的類型. 絕大數(shù)多iOS中的view都創(chuàng)建了CALayer對(duì)象, 并用其來(lái)存儲(chǔ)自身的內(nèi)容. 對(duì)于你自己創(chuàng)建的view來(lái)說(shuō), 默認(rèn)的選擇就夠了, 你也不需要去修改它. 但是在某些特殊的情景下, 你可能會(huì)發(fā)現(xiàn)其他的layer類會(huì)更加適合當(dāng)前的場(chǎng)景. 比如, 在以下場(chǎng)景下, 你可能就會(huì)想改變默認(rèn)的layer類:

  • 你的view使用Metal或是OpenGL ES來(lái)繪圖, 這時(shí)你大概會(huì)使用CAMetalLayerCAEAGLLayer對(duì)象.
  • 有一個(gè)特定的layer類能夠提供更好的表現(xiàn).

改變一個(gè)view的layer所屬的類方法很直接, 代碼如下. 只需要重寫layerClass方法, 并且返回要替換的類就可以了. 在view展示之前, 首先就會(huì)調(diào)用layerClass方法, 使用該方法返回的類來(lái)創(chuàng)建layer對(duì)象, 一旦創(chuàng)建, layer對(duì)象就不能改變.

+ (Class)layerClass {
   return [CAMetalLayer class];
}

注: 想要查看有哪些layer類并且如何使用它們, 請(qǐng)看Different Layer Classes Provide Specialized Behaviors

使用NSView來(lái)改變layer所屬的類

通過(guò)重寫NSView對(duì)象的makeBackingLayer方法也可以改變默認(rèn)的layer對(duì)象所屬的類. 在這個(gè)方法的實(shí)現(xiàn)中, 創(chuàng)建和返回你想要AppKit用來(lái)存儲(chǔ)你view的layer對(duì)象. 當(dāng)你想使用一個(gè)可定制的layer時(shí)可能就會(huì)重寫這個(gè)方法.

不同的layer類提供不同的能力

Core Animation定義了許多標(biāo)準(zhǔn)的layer類. 每一個(gè)類都是有其特定的使用場(chǎng)景的. 具體列表如下:

類名 用法
CAEmitterLayer 用來(lái)實(shí)現(xiàn)一個(gè)基于Core Animation的粒子發(fā)射系統(tǒng). 發(fā)射layer控制粒子的產(chǎn)生.
CAGradientLayer 用來(lái)繪制一個(gè)顏色梯度, 以此來(lái)填充layer的形狀.
CAMetalLayer 用來(lái)設(shè)置一個(gè)可以繪圖的紋理, 以此使用Metal來(lái)渲染layer上的內(nèi)容.
CAEAGLLayer/CAOpenGLLayer 用來(lái)設(shè)置一個(gè)可以使用OpenGL的layer對(duì)象
CAReplicatorLayer 當(dāng)你想能夠自動(dòng)復(fù)制子layer的時(shí)候使用該類.
CAScrollLayer 用來(lái)管理由多個(gè)子layer組成的大片可以滾動(dòng)的區(qū)域.
CAShapeLayer 用來(lái)繪制貝塞爾曲線. 優(yōu)點(diǎn)就是可以繪制基于路徑的形狀.
CATextLayer 用來(lái)渲染純文本或者是屬性文本.
CATiledLayer 用來(lái)管理大圖.
CATransformLayer 用來(lái)渲染3D layer層級(jí).
QCCompositionLayer 用來(lái)渲染Quartz Composer composition.(只針對(duì)OS X)

給layer填充內(nèi)容

layer是數(shù)據(jù)對(duì)象, 其存儲(chǔ)著app想要展現(xiàn)出來(lái)的內(nèi)容. 一個(gè)layer的內(nèi)容是由一個(gè)位圖所構(gòu)成, 位圖中包含著你想要展示的視覺(jué)數(shù)據(jù). 你可以通過(guò)以下三種方式來(lái)為位圖提供內(nèi)容:

  • 直接給layer對(duì)象的contents屬性賦值一個(gè)image對(duì)象. (這種方式適用于layer的內(nèi)容在之后不變, 或是很少改變的情況下使用)
  • 給layer對(duì)象指定一個(gè)代理對(duì)象, 然后讓代理來(lái)繪制layer的內(nèi)容. (這種方式適用于layer上的內(nèi)容可能會(huì)周期性的改變或是內(nèi)容又外部對(duì)象來(lái)提供, 比如view)
  • 自定義一個(gè)layer的子類, 然后重寫它的繪圖方法. (這種方式適用于當(dāng)你必須要自定義layer的子類, 或者你想要改變layer自帶的基本的繪制功能時(shí)使用)

只有當(dāng)你是自己手動(dòng)創(chuàng)建layer對(duì)象的時(shí)候你才需要關(guān)注給layer對(duì)象提供內(nèi)容. 如果你的app只包含基于layer的view, 那你就不需要關(guān)注上面的三種能夠給layer對(duì)象提供內(nèi)容的方法, 因?yàn)橄到y(tǒng)會(huì)以最有效的方式為基于layer的view提供layer所需要的內(nèi)容.

用Image作為layer的內(nèi)容

因?yàn)橐粋€(gè)layer恰恰就是一個(gè)管理位圖的容器, 所以你可以直接把圖片賦值到layer的contents屬性上. 直接賦值圖片到layer上是非常簡(jiǎn)單的, 這樣你可以精確的指定任何圖片. layer對(duì)象會(huì)直接使用你賦值過(guò)來(lái)的圖片, 并且不會(huì)去創(chuàng)建圖片的副本. 當(dāng)你的app在多個(gè)不同的地方使用同一張圖片的時(shí)候, 這樣的處理方式可以有效的節(jié)約內(nèi)存.

賦值到layer上的圖片必須是CGImageRef類型的(OS X v10.6及之后的版本, 也可以賦值NSImage類型的圖片). 當(dāng)賦值圖片的時(shí)候, 需要注意的就是圖片的分辨率要和顯示圖片的設(shè)備的分辨率匹配. 如果是配備了Retina屏幕的設(shè)備, 你可能還需要調(diào)整圖片的contentsScale屬性. 關(guān)于分辨率更多的內(nèi)容, 可以查看Working with High-Resolution Images.

使用代理為layer提供內(nèi)容

如果layer上的內(nèi)容是動(dòng)態(tài)展示的, 即需要經(jīng)常變化的, 這種情況下你就可以使用代理來(lái)為layer提供所需要的內(nèi)容. 在要展示內(nèi)容的時(shí)候, layer就會(huì)呼叫代理讓其來(lái)展示內(nèi)容:

  • 如果代理實(shí)現(xiàn)了displayLayer:方法, 那么就需要在該方法中創(chuàng)建位圖, 并且把位圖賦值到layer的contents屬性上.
  • 如果代理實(shí)現(xiàn)了drawLayer:inContext:方法, Core Animation會(huì)創(chuàng)建位圖和一個(gè)圖形上下文, 用來(lái)繪制位圖, 然后會(huì)調(diào)用代理的方法來(lái)填充位圖. 代理方法所需要做的就是在圖形上下文上面繪制內(nèi)容.

代理對(duì)象必須實(shí)現(xiàn)下列方法中的任意一個(gè), displayLayer:drawLayer:inContext:. 如果兩個(gè)方法全部都實(shí)現(xiàn)了的話, layer也只會(huì)調(diào)用displayLayer:方法.

當(dāng)你的app想載入或是創(chuàng)建它想要展示的內(nèi)容時(shí), 適合重寫displayLayer:方法. 下面的代碼就簡(jiǎn)單的實(shí)現(xiàn)了該方法. 其中代理使用了一個(gè)幫助對(duì)象來(lái)加載和展示它所需要的圖片. 代理方法是基于內(nèi)部狀態(tài)來(lái)選擇到底展示哪張圖片, 這里的內(nèi)部狀態(tài)指的是一個(gè)自定義的屬性displayYesImage.

- (void)displayLayer:(CALayer *)theLayer {
    // 檢查內(nèi)部狀態(tài)
    if (self.displayYesImage) {
        // 展示相應(yīng)的圖片
        theLayer.contents = [someHelperObject loadStateYesImage];
    }
    else {
        // 展示其它的圖片
        theLayer.contents = [someHelperObject loadStateNoImage];
    }
}

如果你還沒(méi)有預(yù)先渲染好的圖片或是一個(gè)幫助對(duì)象來(lái)幫你創(chuàng)建位圖, 你的代理可以動(dòng)態(tài)的使用drawLayer:inContext:方法繪制內(nèi)容. 看不懂就看下面的例子, 代理使用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);
 
    // 釋放路徑
    CFRelease(thePath);
}

對(duì)于擁有自定義的內(nèi)容且基于layer的view來(lái)說(shuō), 你就應(yīng)該重寫view的方法來(lái)進(jìn)行繪制. 基于layer的view會(huì)自動(dòng)成為其代理, 并且實(shí)現(xiàn)所需要的代理方法, 你不應(yīng)該改變這個(gè)配置. 相反, 你應(yīng)該實(shí)現(xiàn)你自己view的drawRect:方法來(lái)繪制你的內(nèi)容.

通過(guò)子類給layer提供內(nèi)容

如果你實(shí)現(xiàn)了一個(gè)自定義的layer類, 那么你就可以重寫繪圖的方法. 對(duì)于一個(gè)layer對(duì)象來(lái)說(shuō), 為自己創(chuàng)建自定義的內(nèi)容是挺不常見(jiàn)的, 但是確實(shí)可以這樣做. 比如, CATiledLayer類可以通過(guò)將大圖分解成小部分然后再進(jìn)行管理和渲染. 因?yàn)橹挥衛(wèi)ayer知道何時(shí)渲染哪個(gè)部分, 所以layer直接管理著繪圖行為.

當(dāng)使用子類的時(shí)候, 可以使用下列方法來(lái)給layer繪制內(nèi)容:

  • 重寫layer的display方法, 用它直接設(shè)置layer的contents屬性.
  • 重寫layer的drawInContext:方法, 用它直接在上下文中繪制內(nèi)容.

你要重寫哪個(gè)方法取決于你想如何控制繪制的過(guò)程. display方法是更新layer上內(nèi)容的主要入口, 所以重寫該方法意味著你可以對(duì)繪制過(guò)程進(jìn)行全權(quán)掌控. 并且重寫該方法也意味著你需要?jiǎng)?chuàng)建CGImageRef并且將其賦值給contents屬性. 如果你只是想繪制, 或是只是想讓你的layer管理繪制操作, 你可以重寫drawInContext:方法.

調(diào)整你提供的內(nèi)容

當(dāng)把圖片賦值給layer的contents屬性時(shí), layer的contentsGravity屬性會(huì)決定圖片如何操作以適應(yīng)當(dāng)前的bounds. 默認(rèn)情況下, 如果圖片比現(xiàn)在的bounds大或者小, layer對(duì)象會(huì)相應(yīng)的縮放圖片. 如果layerbounds的寬高比和圖片的寬高比不同, 圖片可能就會(huì)扭曲. 所以你必須使用contentsGravity屬性來(lái)確保你的內(nèi)容能以最佳的方式展現(xiàn).

能賦值給contentsGravity屬性的值分為兩類:

  • 基于位置的重力常量允許你在沒(méi)有縮放圖片的情況下可以把圖片擺放在layer所在的矩形區(qū)域里的特定邊或角上.
  • 基于尺度的重力常量允許你可以拉伸圖片, 并且可以選擇維持圖片的寬高比或不維持.

下圖展示了基于位置的重力設(shè)置是如何影響圖片的. 除了kCAGravityCenter常量, 剩下的都可以指定圖片到任意邊角. 而kCAGravityCenter會(huì)將圖片相對(duì)于layer居中. 這些選擇都不會(huì)改變圖片的大小, 所以圖片永遠(yuǎn)都是按照它的原始尺寸進(jìn)行渲染. 如果圖片大于layerbounds的大小, 那么多出來(lái)的部分就裁剪掉了, 如果小的話, 就覆蓋不完layer, 如果layer存在背景色的話, 就會(huì)看見(jiàn).

下圖展示了基于尺度的重力設(shè)置是如何影響圖片的. 如果圖片和layer所在的矩形區(qū)域不匹配, 所有的選項(xiàng)都縮放圖片. 選項(xiàng)間的區(qū)別在于圖片的原始寬高比有沒(méi)有改變. 默認(rèn)情況下, layer的contentsGravity屬性是被設(shè)置成kCAGravityResize的, 這也是唯一一個(gè)不保存圖片原始寬高比的一個(gè)選項(xiàng).

調(diào)整layer的視覺(jué)樣式和表現(xiàn)

layer對(duì)象有自己內(nèi)置的視覺(jué)裝飾, 比如邊界和背景色, 你可以用來(lái)填充layer的內(nèi)容. 因?yàn)檫@些視覺(jué)裝飾不需要渲染, 所以在一些情景中可以將layer作為一個(gè)獨(dú)立的實(shí)體來(lái)使用. 你需要做的就是給屬性賦值, 然后layer就會(huì)處理必要的繪制, 包括動(dòng)畫. 關(guān)于layer其他的視覺(jué)裝飾, 可以看Layer Style Property Animations.

layer有自己的背景色和邊界

一個(gè)layer除了其自身上的圖片外, 其還可以填充背景色和邊框. 背景色在圖片的下面, 邊框則在圖片的上面, 如下圖. 如果layer包含子layer, 其也會(huì)顯示在邊框的下面. 因?yàn)楸尘吧秋@示在圖片的下面, 所以背景色會(huì)對(duì)圖片產(chǎn)生一些影響.

給layer設(shè)置背景色和邊框的示例代碼如下:

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

如果你把layer的背景色設(shè)置成了一個(gè)不透明的顏色, 那么最好把layer的opaque屬性值設(shè)置成YES. 這樣做是因?yàn)楫?dāng)把layer顯示到屏幕上時(shí), 可以提高顯示效果. 如果layer有一個(gè)非0的圓角半徑, 那么你也可以不設(shè)置opaque屬性.

layer支持圓角半徑

通過(guò)添加圓角半徑可以給layer創(chuàng)建一個(gè)圓角矩形的效果. 圓角半徑是一個(gè)視覺(jué)裝飾, 它可以掩蓋layer所處的矩形區(qū)域的角, 如下圖所示. 因?yàn)樗婕暗酵该餮谏w, 所以圓角半徑并不會(huì)影響顯示在layer上的圖片, 除非masksToBounds屬性設(shè)置成了YSE. 然而, 圓角半徑總是會(huì)影響layer的背景色和邊框的繪制.

在layer上應(yīng)用圓角半徑的話, 需要指定cornerRadius屬性的值. 你指定的這個(gè)半徑的值是以點(diǎn)為單位計(jì)量的, 并且會(huì)被應(yīng)用到這個(gè)layer的所有4個(gè)角上.

layer支持內(nèi)置的陰影

CALayer類包含了一些屬性來(lái)設(shè)置陰影效果. 陰影效果在一些情景下對(duì)你的app也是十分有必要的, 它也屬于是視覺(jué)裝飾. 通過(guò)layer, 你可以控制陰影的顏色, 相對(duì)于layer上內(nèi)容的位置, 透明度以及形狀.

不透明度的值默認(rèn)是0, 這就是完全看不見(jiàn)陰影效果. 當(dāng)不透明度的值變?yōu)榉?時(shí), Core Animation就會(huì)開始繪制陰影. 因?yàn)槟J(rèn)情況下, 陰影的位置是在layer下面的, 所以在你想看見(jiàn)陰影效果之前, 你需要改變陰影的偏移量. 并且需要牢記的一點(diǎn)是, 偏移量的值是基于layer自身所處的坐標(biāo)系統(tǒng)內(nèi)而言的. 并且在iOS和OS X中是不同的, 下圖展示了下邊緣和右邊緣帶陰影效果的樣子. 在iOS中, 在y軸方向的偏移量需要指定正值; OS X中則是負(fù)值.

當(dāng)給layer添加陰影時(shí), 陰影就是layer內(nèi)容的一部分, 但實(shí)際上它是延伸在layer所處的矩形以外的. 所以, 如果你設(shè)置了layer的masksToBounds屬性, 陰影效果就會(huì)從邊緣被裁剪掉. 而如果你的layer包含了任何透明的內(nèi)容, 那么就會(huì)導(dǎo)致一種奇怪的效果, 即處在layer下面的陰影部分是可以看見(jiàn)的, 但是延伸在外面的卻看不見(jiàn)了. 如果你又想看見(jiàn)陰影, 又想設(shè)置masksToBounds, 你可以使用兩個(gè)layer而不是一個(gè). 把掩蓋加到包含內(nèi)容的layer上, 然后再把這個(gè)layer鑲嵌到第二個(gè)同樣大小并且?guī)в嘘幱靶Ч膌ayer里.

至于陰影如何應(yīng)用到layer上, 看一下Shadow Properties.

給layer添加一個(gè)自定義的屬性

CAAnimationCALayer這兩個(gè)類支持使用KVC來(lái)設(shè)置自定義的屬性. 你可以通過(guò)KVC給layer添加數(shù)據(jù), 然后通過(guò)特定的鍵來(lái)檢索. 關(guān)于具體如何設(shè)置和獲得自定義的屬性, 請(qǐng)看Key-Value Coding Compliant Container Classes.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)載:http://www.itdecent.cn/p/32fcadd12108 每個(gè)UIView有一個(gè)伙伴稱為l...
    F麥子閱讀 6,568評(píng)論 0 13
  • 每個(gè)UIView有一個(gè)伙伴稱為layer,一個(gè)CALayer。UIView實(shí)際上并沒(méi)有把自己畫到屏幕上;它繪制本身...
    shenzhenboy閱讀 3,252評(píng)論 0 17
  • 在iOS中隨處都可以看到絢麗的動(dòng)畫效果,實(shí)現(xiàn)這些動(dòng)畫的過(guò)程并不復(fù)雜,今天將帶大家一窺ios動(dòng)畫全貌。在這里你可以看...
    每天刷兩次牙閱讀 8,688評(píng)論 6 30
  • 在iOS中隨處都可以看到絢麗的動(dòng)畫效果,實(shí)現(xiàn)這些動(dòng)畫的過(guò)程并不復(fù)雜,今天將帶大家一窺iOS動(dòng)畫全貌。在這里你可以看...
    F麥子閱讀 5,267評(píng)論 5 13
  • 美眉們做完半永久紋眉后: 每天都收到美女們?cè)S許多多,超級(jí)可愛(ài),無(wú)比開心得反饋信息……親,開始蛻變啦!親,眉毛開始卷...
    李美霞素簡(jiǎn)紋繡閱讀 639評(píng)論 0 0

友情鏈接更多精彩內(nèi)容