這一章節(jié)我們將來(lái)探索 CALayer
的寄宿圖(即圖層中包含的圖)。
contents屬性
CALayer
有一個(gè)屬性叫做 contents
,這個(gè)屬性的類型被定義為id
,意味著它可以 是任何類型的對(duì)象渗饮。在這種情況下但汞,你可以給屬性賦任何值,你的app
仍然能夠編譯通過(guò)互站。但是私蕾,在實(shí)踐中,如果你給賦的不是CGImage
胡桃, 那么你得到的圖層將是空白的踩叭。
contents
這個(gè)奇怪的表現(xiàn)是由Mac OS
的歷史原因造成的。它之所以被定義為id
類型翠胰,是因?yàn)樵?code>Mac OS系統(tǒng)上容贝,這個(gè)屬性對(duì)CGImage
和NSImage
類型的值都起作 用。如果你試圖在iOS
平臺(tái)上UIImage
的值賦給它之景,只能得到一個(gè)空白的圖層斤富。 一些初識(shí)Core Animation的iOS
開發(fā)者可能會(huì)對(duì)這個(gè)感到困惑。
頭疼的不僅僅是我們剛才提到的這個(gè)問(wèn)題锻狗。事實(shí)上满力,你真正要賦值的類型應(yīng)該是CGImageRef
,它是一個(gè)指向CGImage
結(jié)構(gòu)的指針轻纪。UIImage
有一個(gè)CGImage
屬 性油额,它返回一個(gè)"CGImageRef
",如果你想把這個(gè)值直接賦值給CALayer
的 contents
,那你將會(huì)得到一個(gè)編譯錯(cuò)誤刻帚。因?yàn)?code>CGImageRef并不是一個(gè)真正的 Cocoa
對(duì)象悔耘,而是一個(gè)Core Foundation
類型。
盡管Core Foundation
類型跟Cocoa
對(duì)象在運(yùn)行時(shí)貌似很像(被稱作toll-free bridging
)我擂,他們并不是類型兼容的衬以,不過(guò)你可以通過(guò)bridged
關(guān)鍵字轉(zhuǎn)換。如果要 給圖層的寄宿圖賦值校摩,你可以按照以下這個(gè)方法:
layer.contents = (__bridge id)image.CGImage;
如果你沒有使用ARC
(自動(dòng)引用計(jì)數(shù))看峻,你就不需要__bridge
這部分。但是衙吩,你干 嘛不用ARC
?!
那么我們就直接把UIView
的宿主圖層的 contents
屬性設(shè)置成圖片互妓。
- (void)viewDidLoad{
[super viewDidLoad]; //load an image
UIImage *image = [UIImage imageNamed:@"Snowman.png"];
//add it directly to our view's layer
self.layerView.layer.contents = (__bridge id)image.CGImage;
}
我們用這些簡(jiǎn)單的代碼做了一件很有趣的事情:我們利用CALayer
在一個(gè)普通的 UIView
中顯示了一張圖片。這不是一個(gè)UIImageView
坤塞,它不是我們通常用來(lái)展示圖片的方法冯勉。通過(guò)直接操作圖層,我們使用了一些新的函數(shù)摹芙,使得UIView
更加有趣 了灼狰。
contentGravity
為了適應(yīng)這個(gè)視圖,它有一點(diǎn)點(diǎn)被拉伸了浮禾。在使用UIImageView
的時(shí)候遇到過(guò)同樣的問(wèn)題交胚,解決方法就是把 contentMode
屬性設(shè)置成更合適的 值份汗,像這樣:
imageView.contentMode = UIViewContentModeScaleAspectFit;
不過(guò)UIView
大多數(shù)視覺相關(guān)的屬性比如contentMode
,對(duì)這些屬性的操作其實(shí)是對(duì)對(duì)應(yīng)圖層的操作蝴簇。
CALayer
與contentMode
對(duì)應(yīng)的屬性叫做 contentsGravity
杯活,但是它是一個(gè) NSString
類型,而不是像對(duì)應(yīng)的UIKit
部分熬词,那里面的值是枚
舉旁钧。 contentsGravity
可選的常量值有以下一些:
- kCAGravityCenter
- kCAGravityTop
- kCAGravityBottom
- kCAGravityLeft
- kCAGravityRight
- kCAGravityTopLeft
- kCAGravityTopRight
- kCAGravityBottomLeft
- kCAGravityBottomRight
- kCAGravityResize
- kCAGravityResizeAspect
- kCAGravityResizeAspectFill
和 cotentMode
一樣, contentsGravity
的目的是為了決定內(nèi)容在圖層的邊界中怎么對(duì)齊互拾,我們將使用kCAGravityResizeAspect
均践,它的效果等同于 UIViewContentModeScaleAspectFit, 同時(shí)它還能在圖層中等比例拉伸以適應(yīng)圖層 的邊界摩幔。
self.layerView.layer.contentsGravity = kCAGravityResizeAspect;
contentsScale
contentsScale
屬性定義了寄宿圖的像素尺寸和視圖大小的比例,默認(rèn)情況下它 是一個(gè)值為1.0的浮點(diǎn)數(shù)鞭铆。
contentsScale
的目的并不是那么明顯或衡。它并不是總會(huì)對(duì)屏幕上的寄宿圖有影 響。如果你嘗試對(duì)我們的例子設(shè)置不同的值车遂,你就會(huì)發(fā)現(xiàn)根本沒任何影響封断。因?yàn)?contents
由于設(shè)置了contentsGravity
屬性,所以它已經(jīng)被拉伸以適應(yīng)圖層的邊界舶担。
如果你只是單純地想放大圖層的 圖片坡疼,你可以通過(guò)使用圖層的transform
和affineTransform
屬性來(lái)達(dá)到這個(gè)目的,這(指放大)也不是 contengsScale
的目的所在.
contentsScale
屬性其實(shí)屬于支持高分辨率(又稱Hi-DPI
或Retina
)屏幕機(jī)制的 一部分衣陶。它用來(lái)判斷在繪制圖層的時(shí)候應(yīng)該為寄宿圖創(chuàng)建的空間大小柄瑰,和需要顯示的圖片的拉伸度(假設(shè)并沒有設(shè)置contentsGravity
屬性)。UIView
有一個(gè)類似 功能但是非常少用到的contentScaleFactor
屬性剪况。
如果 contentsScale
設(shè)置為1.0教沾,將會(huì)以每個(gè)點(diǎn)1個(gè)像素繪制圖片,如果設(shè)置為 2.0译断,則會(huì)以每個(gè)點(diǎn)2個(gè)像素繪制圖片授翻,這就是我們熟知的Retina
屏幕。(如果你對(duì)像素和點(diǎn)的概念不是很清楚的話孙咪,這個(gè)章節(jié)的后面部分將會(huì)對(duì)此做出解釋)堪唐。
這并不會(huì)對(duì)我們?cè)谑褂?code>kCAGravityResizeAspect時(shí)產(chǎn)生任何影響,因?yàn)樗褪抢?圖片以適應(yīng)圖層而已翎蹈,根本不會(huì)考慮到分辨率問(wèn)題淮菠。但是如果我們把 contentsGravity
設(shè)置為kCAGravityCenter
(這個(gè)值并不會(huì)拉伸圖片),那將會(huì)有很明顯的變化.
- (void)viewDidLoad{
[super viewDidLoad]; //load an image
self.layerView.layer.contentsGravity = kCAGravityCenter;
//set the contentsScale to match image
self.layerView.layer.contentsScale = image.scale;
}
當(dāng)用代碼的方式來(lái)處理寄宿圖的時(shí)候荤堪,一定要記住要手動(dòng)的設(shè)置圖層的 contentsScale
屬性兜材,否則理澎,你的圖片在Retina
設(shè)備上就顯示得不正確啦。代 碼如下:
layer.contentsScale = [UIScreen mainScreen].scale;
maskToBounds
默認(rèn)情況下曙寡,UIView仍然會(huì)繪制超過(guò)邊界的內(nèi)容或是子視 圖糠爬,在CALayer
下也是這樣的。
UIView
有一個(gè)叫做clipsToBounds
的屬性可以用來(lái)決定是否顯示超出邊界的內(nèi)容举庶,CALayer
對(duì)應(yīng)的屬性叫做masksToBounds
执隧,把它設(shè)置為YES
。
contentsRect
CALayer
的 contentsRect
屬性允許我們?cè)趫D層邊框里顯示寄宿圖的一個(gè)子域户侥。這涉及到圖片是如何顯示和拉伸的镀琉,所以要比 contentsGravity
靈活多了
和bounds
, frame
不同蕊唐, contentsRect
不是按點(diǎn)來(lái)計(jì)算的屋摔,它使用了單位坐標(biāo),單位坐標(biāo)指定在0到1之間替梨,是一個(gè)相對(duì)值(像素和點(diǎn)就是絕對(duì)值)钓试。所以他們是相對(duì)與寄宿圖的尺寸的。iOS
使用了以下的坐標(biāo)系統(tǒng):
點(diǎn) —— 在
iOS
和Mac OS
中最常見的坐標(biāo)體系副瀑。點(diǎn)就像是虛擬的像素弓熏,也被稱作邏輯像素。在標(biāo)準(zhǔn)設(shè)備上糠睡,一個(gè)點(diǎn)就是一個(gè)像素挽鞠,但是在Retina
設(shè)備上,一 個(gè)點(diǎn)等于2*2
個(gè)像素狈孔。iOS
用點(diǎn)作為屏幕的坐標(biāo)測(cè)算體系就是為了在Retina
設(shè)備和普通設(shè)備上能有一致的視覺效果信认。像素 —— 物理像素坐標(biāo)并不會(huì)用來(lái)屏幕布局,但是仍然與圖片有相對(duì)關(guān)系均抽。
UIImage
是一個(gè)屏幕分辨率解決方案狮杨,所以指定點(diǎn)來(lái)度量大小。但是一些底層的圖片表示如CGImage
就會(huì)使用像素到忽,所以你要清楚在Retina
設(shè)備和普通設(shè)備上橄教,他們表現(xiàn)出來(lái)了不同的大小。單位 —— 對(duì)于與圖片大小或是圖層邊界相關(guān)的顯示喘漏,單位坐標(biāo)是一個(gè)方便的度量方式护蝶,當(dāng)大小改變的時(shí)候,也不需要再次調(diào)整翩迈。單位坐標(biāo)在
OpenGL
這種紋理坐標(biāo)系統(tǒng)中用得很多持灰,Core Animation
中也用到了單位坐標(biāo)。
默認(rèn)的 contentsRect
是{0, 0, 1, 1}
负饲,這意味著整個(gè)寄宿圖默認(rèn)都是可見的堤魁,如果 我們指定一個(gè)小一點(diǎn)的矩形{0, 0, 0.5, 0.5}
喂链,圖片就會(huì)被裁剪。
事實(shí)上給contentsRect
設(shè)置一個(gè)負(fù)數(shù)的原點(diǎn)或是大于{1, 1}
的尺寸也是可以的妥泉。這種情況下椭微,最外面的像素會(huì)被拉伸以填充剩下的區(qū)域。
contentsRect
在app
中最有趣的地方在于一個(gè)叫做image sprites
(圖片拼合)的 用法盲链。如果你有游戲編程的經(jīng)驗(yàn)蝇率,那么你一定對(duì)圖片拼合的概念很熟悉,圖片能夠在屏幕上獨(dú)立地變更位置刽沾。拋開游戲編程不談本慕,這個(gè)技術(shù)常用來(lái)指代載入拼合的圖 片,跟移動(dòng)圖片一點(diǎn)關(guān)系也沒有侧漓。
典型地锅尘,圖片拼合后可以打包整合到一張大圖上一次性載入。相比多次載入不同的圖片布蔗,這樣做能夠帶來(lái)很多方面的好處:內(nèi)存使用藤违,載入時(shí)間,渲染性能等等
2D
游戲引擎入Cocos2D使用了拼合技術(shù)何鸡,它使用OpenGL
來(lái)顯示圖片。不過(guò)我們可以使用拼合在一個(gè)普通的UIKit
應(yīng)用中牛欢,對(duì)!就是使用contentsRect
contentsCenter
我們介紹的最后一個(gè)和內(nèi)容有關(guān)的屬性是 contentsCenter
骡男,看名字你可能 會(huì)以為它可能跟圖片的位置有關(guān),不過(guò)這名字著實(shí)誤導(dǎo)了你傍睹。 contentsCenter
其實(shí)是一個(gè)CGRect
隔盛,它定義了一個(gè)固定的邊框和一個(gè)在圖層上可拉伸的區(qū)域。 改變 contentsCenter
的值并不會(huì)影響到寄宿圖的顯示拾稳,除非這個(gè)圖層的大小改變了吮炕,你才看得到效果。
默認(rèn)情況下访得, contentsCenter
是{0, 0, 1, 1}龙亲,這意味著如果大小(由'contentsGravity' 決定)改變了,那么寄宿圖將會(huì)均勻地拉伸開。但是如果我們?cè)黾釉c(diǎn)的值并減小尺寸悍抑。我們會(huì)在圖片的周圍創(chuàng)造一個(gè)邊框鳄炉。如圖展示了 contentsCenter
設(shè)置為{0.25, 0.25, 0.5, 0.5}
的效果。
這意味著我們可以隨意重設(shè)尺寸搜骡,邊框仍然會(huì)是連續(xù)的拂盯。他工作起來(lái)的效果和 UIImage
里的-resizableImageWithCapInsets:
方法效果非常類似,只是它可以運(yùn)用到任何寄宿圖记靡,甚至包括在Core Graphics
運(yùn)行時(shí)繪制的圖形谈竿。
Custom Drawing
給 contents
賦CGImage
的值不是唯一的設(shè)置寄宿圖的方法团驱。我們也可以直接 用Core Graphics
直接繪制寄宿圖。能夠通過(guò)繼承UIView
并實(shí)現(xiàn) -drawRect:
方法 來(lái)自定義繪制空凸。
-drawRect:
方法沒有默認(rèn)的實(shí)現(xiàn)嚎花,因?yàn)閷?duì)UIView
來(lái)說(shuō),寄宿圖并不是必須 的劫恒,它不在意那到底是單調(diào)的顏色還是有一個(gè)圖片的實(shí)例贩幻。如果UIView
檢測(cè)到 - drawRect:
方法被調(diào)用了,它就會(huì)為視圖分配一個(gè)寄宿圖两嘴,這個(gè)寄宿圖的像素尺寸等于視圖大小乘以 contentsScale
的值丛楚。
如果你不需要寄宿圖,那就不要?jiǎng)?chuàng)建這個(gè)方法了憔辫,這會(huì)造成CPU
資源和內(nèi)存的浪費(fèi)趣些,這也是為什么蘋果建議:如果沒有自定義繪制的任務(wù)就不要在子類中寫一個(gè)空 的-drawRect:
方法。
當(dāng)視圖在屏幕上出現(xiàn)的時(shí)候-drawRect:
方法就會(huì)被自動(dòng)調(diào)用贰您。 - drawRect:
方法里面的代碼利用Core Graphics
去繪制一個(gè)寄宿圖坏平,然后內(nèi)容就會(huì)被緩存起來(lái)直到它需要被更新(通常是因?yàn)殚_發(fā)者調(diào)用了-setNeedsDisplay
方 法,盡管影響到表現(xiàn)效果的屬性值被更改時(shí)锦亦,一些視圖類型會(huì)被自動(dòng)重繪舶替,如 bounds
屬性)。雖然-drawRect:
方法是一個(gè)UIView
方法杠园,事實(shí)上都是底層 的CALayer
安排了重繪工作和保存了因此產(chǎn)生的圖片顾瞪。
CALayer
有一個(gè)可選的 delegate
屬性,實(shí)現(xiàn)了 CALayerDelegate
協(xié)議抛蚁,當(dāng)CALayer
需要一個(gè)內(nèi)容特定的信息時(shí)陈醒,就會(huì)從協(xié)議中請(qǐng)求。CALayerDelegate
是一 個(gè)非正式協(xié)議瞧甩,其實(shí)就是說(shuō)沒有CALayerDelegate @protocol
可以讓你在類里面引 用啦钉跷。你只需要調(diào)用你想調(diào)用的方法,CALayer
會(huì)幫你做剩下的肚逸。( delegate
屬性被聲明為id
類型爷辙,所有的代理方法都是可選的)。
當(dāng)需要被重繪時(shí)朦促,CALayer
會(huì)請(qǐng)求它的代理給他一個(gè)寄宿圖來(lái)顯示犬钢。它通過(guò)調(diào)用 下面這個(gè)方法做到的:
- (void)displayLayer:(CALayerCALayer *)layer;
趁著這個(gè)機(jī)會(huì),如果代理想直接設(shè)置contents
屬性的話,它就可以這么做,不然沒有別的方法可以調(diào)用了思灰。如果代理不實(shí)現(xiàn) -displayLayer:
方法,CALaye
r就會(huì)轉(zhuǎn)而嘗試調(diào)用下面這個(gè)方法:
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
在調(diào)用這個(gè)方法之前玷犹,CALayer
創(chuàng)建了一個(gè)合適尺寸的空寄宿圖(尺寸由 bounds
和 contentsScale
決定)和一個(gè)Core Graphics
的繪制上下文環(huán)境, 為繪制寄宿圖做準(zhǔn)備,他作為ctx
參數(shù)傳入歹颓。
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//create sublayer
CALayer *blueLayer = [CALayer layer];
blueLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
blueLayer.backgroundColor = [UIColor blueColor].CGColor;
//set controller as layer delegate
blueLayer.delegate = self;
//ensure that layer backing image uses correct scale
[self.layerView.layer addSublayer:blueLayer];
//force layer to redraw
[blueLayer display];
}
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
//draw a thick red circle
CGContextSetLineWidth(ctx, 10.0f);
CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
CGContextStrokeEllipseInRect(ctx, layer.bounds);
}
@end
![Uploading 實(shí)現(xiàn)CALayerDelegate來(lái)繪制圖層_614124.png . . .]
注意一下一些有趣的事情:
- 我們?cè)?code>blueLayer上顯式地調(diào)用了
-display
坯屿。不同于UIView
,當(dāng)圖層顯示在屏幕上時(shí)巍扛,CALayer
不會(huì)自動(dòng)重繪它的內(nèi)容领跛。它把重繪的決定權(quán)交給了開發(fā)者。 - 盡管我們沒有用
masksToBounds
屬性撤奸,繪制的那個(gè)圓仍然沿邊界被裁剪了吠昭。 這是因?yàn)楫?dāng)你使用CALayerDelegate
繪制寄宿圖的時(shí)候,并沒有對(duì)超出邊界外 的內(nèi)容提供繪制支持胧瓜。
現(xiàn)在你理解了CALayerDelegate
矢棚,并知道怎么使用它。但是除非你創(chuàng)建了一個(gè)單獨(dú)的圖層府喳,你幾乎沒有機(jī)會(huì)用到CALayerDelegate
協(xié)議蒲肋。因?yàn)楫?dāng)UIView
創(chuàng)建了它的宿主圖層時(shí),它就會(huì)自動(dòng)地把圖層的delegate
設(shè)置為它自己钝满,并提供了一個(gè) - displayLayer:
的實(shí)現(xiàn)兜粘,那所有的問(wèn)題就都沒了。
當(dāng)使用寄宿了視圖的圖層的時(shí)候弯蚜,你也不必實(shí)現(xiàn)-displayLayer:
和 - drawLayer:inContext:
方法來(lái)繪制你的寄宿圖孔轴。通常做法是實(shí)現(xiàn)UIView
的 - drawRect:
方法,UIView
就會(huì)幫你做完剩下的工作碎捺,包括在需要重繪的時(shí)候調(diào)用 - display
方法路鹰。
總結(jié)
本章介紹了寄宿圖和一些相關(guān)的屬性。你學(xué)到了如何顯示和放置圖片牵寺, 使用拼合技術(shù)來(lái)顯示悍引, 以及用CALayerDelegate
和Core Graphics
來(lái)繪制圖層內(nèi)容恩脂。