目錄
- CAShapLayer
- CATextLayer
- CATransformLayer
- CAGradientLayer
- CAReplicatorLayer
- CAScrollLayer
- CATiledLayer
- CAEmitterLayer
- CAEAGLLayer
- AVPlayerLayer
- 總結(jié)
序言
到目前為止晚岭,我們已經(jīng)探討過CALayer
類了揉稚,同時(shí)我們也了解到了一些非常有用的繪圖
和動(dòng)畫功能
。但是Core Animation
圖層不僅僅能作用于圖片和顏色而已抹剩。本章就會(huì)學(xué)習(xí)其他的一些圖層類嗽测,進(jìn)一步擴(kuò)展使用Core Animation
繪圖的能力绪励。
一 CAShapeLayer
CAShapeLayer
是一個(gè)通過矢量圖形
而不是bitmap
來繪制的圖層子類。你指定諸如顏色
和線寬
等屬性唠粥,用CGPath
來定義想要繪制的圖形疏魏,最后CAShapeLayer
就自動(dòng)渲染出來了。當(dāng)然晤愧,你也可以用Core Graphics
直接向原始的CALyer
的內(nèi)容中繪制一個(gè)路徑大莫,相比直下,使用CAShapeLayer
有以下一些優(yōu)點(diǎn):
-
渲染快速
官份。CAShapeLayer使用了硬件加速只厘,繪制同一圖形會(huì)比用Core Graphics快很多。 -
高效使用內(nèi)存
舅巷。一個(gè)CAShapeLayer不需要像普通CALayer一樣創(chuàng)建一個(gè)寄宿圖形羔味,所以無論有多大,都不會(huì)占用太多的內(nèi)存悄谐。 -
不會(huì)被圖層邊界剪裁掉
介评。一個(gè)CAShapeLayer可以在邊界之外繪制。你的圖層路徑不會(huì)像在使用Core Graphics的普通CALayer一樣被剪裁掉(如我們?cè)诘诙滤姡?/li> -
不會(huì)出現(xiàn)像素化
。當(dāng)你給CAShapeLayer做3D變換時(shí)们陆,它不像一個(gè)有寄宿圖的普通圖層一樣變得像素化寒瓦。
1.1 創(chuàng)建一個(gè)CGPath
CAShapeLayer
可以用來繪制所有能夠通過CGPath
來表示的形狀。這個(gè)形狀不一定要閉合坪仇,圖層路徑也不一定要不可破杂腰,事實(shí)上你可以在一個(gè)圖層上繪制好幾個(gè)不同的形狀。你可以控制一些屬性比如lineWith
(線寬椅文,用點(diǎn)表示單位)喂很,lineCap
(線條結(jié)尾的樣子),和lineJoin
(線條之間的結(jié)合點(diǎn)的樣子)皆刺;但是在圖層層面你只有一次機(jī)會(huì)設(shè)置這些屬性少辣。如果你想用不同顏色或風(fēng)格來繪制多個(gè)形狀,就不得不為每個(gè)形狀準(zhǔn)備一個(gè)圖層了羡蛾。
下面代碼用一個(gè)CAShapeLayer
渲染一個(gè)簡(jiǎn)單的火柴人漓帅。CAShapeLayer
屬性是CGPathRef
類型,但是我們用UIBezierPath
幫助類創(chuàng)建了圖層路徑痴怨,這樣我們就不用考慮人工釋放CGPath了忙干。
- (void)shaperLayer {
UIBezierPath *path = [[UIBezierPath alloc] init];
[path moveToPoint:CGPointMake(175, 100)];
// add path
[path addArcWithCenter:CGPointMake(150, 100) radius:25 startAngle:0 endAngle:2*M_PI clockwise:YES];
[path moveToPoint:CGPointMake(150, 125)];
[path addLineToPoint:CGPointMake(150, 175)];
[path addLineToPoint:CGPointMake(125, 225)];
[path moveToPoint:CGPointMake(150, 175)];
[path addLineToPoint:CGPointMake(175, 225)];
[path moveToPoint:CGPointMake(100, 150)];
[path addLineToPoint:CGPointMake(200, 150)];
// create shape layer
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.strokeColor = [UIColor redColor].CGColor;
shapeLayer.fillColor = [UIColor clearColor].CGColor;
shapeLayer.lineWidth = 5;
shapeLayer.lineJoin = kCALineJoinRound;
shapeLayer.lineCap = kCALineCapRound;
shapeLayer.path = path.CGPath;
// add it to our view
[self.view.layer addSublayer:shapeLayer];
}
- 運(yùn)行結(jié)果如下
1.2 圓角
前面里面提到了CAShapeLayer
為創(chuàng)建圓角視圖
提供了一個(gè)方法,就是CALayer
的cornerRadius
屬性浪藻。雖然使用CAShapeLayer
類需要更多的工作捐迫,但是它有一個(gè)優(yōu)勢(shì)就是可以單獨(dú)指定每個(gè)角
。
我們創(chuàng)建圓角矩形其實(shí)就是人工繪制單獨(dú)的直線和弧度爱葵,但是事實(shí)上UIBezierPath
有自動(dòng)繪制圓角矩形的構(gòu)造方法施戴,下面這段代碼繪制了一個(gè)有三個(gè)圓角一個(gè)直角的矩形:
/// 三個(gè)圓角的矩形
- (void)createThreeRadiusRound {
// define path parameters
CGRect rect = CGRectMake(100, 100, 100, 100);
CGSize radii = CGSizeMake(20, 20);
UIRectCorner corners = UIRectCornerTopRight | UIRectCornerBottomRight | UIRectCornerBottomLeft;
// create path
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:corners cornerRadii:radii];
// create shape layer
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.strokeColor = [UIColor redColor].CGColor;
shapeLayer.fillColor = [UIColor clearColor].CGColor;
shapeLayer.lineWidth = 5;
shapeLayer.lineJoin = kCALineJoinRound;
shapeLayer.lineCap = kCALineCapRound;
shapeLayer.path = path.CGPath;
// add it to our view
[self.view.layer addSublayer:shapeLayer];
}
- 運(yùn)行結(jié)果如下
二 CATextLayer
用戶界面是無法從一個(gè)單獨(dú)的圖片里面構(gòu)建的。一個(gè)設(shè)計(jì)良好的圖標(biāo)能夠很好地表現(xiàn)一個(gè)按鈕或控件的意圖钧惧,不過你遲早都要需要一個(gè)不錯(cuò)的老式風(fēng)格的文本標(biāo)簽暇韧。
如果你想在一個(gè)圖層里面顯示文字,完全可以借助圖層代理直接將字符串使用Graphics
寫入圖層的內(nèi)容(這就是UILabel
的精髓)浓瞪。如果越過寄宿于圖層的視圖懈玻,直接在圖層上操作,那其實(shí)相當(dāng)繁瑣乾颁。你要為每一個(gè)顯示文字的圖層創(chuàng)建一個(gè)能像圖層代理一樣工作的類涂乌,還要邏輯上判斷哪個(gè)圖層需要顯示哪個(gè)字符串,更別提還要記錄不同的字體英岭,顏色等一系列亂七八糟的東西湾盒。
萬幸的是這些都是不必要的,Core Animation
提供了一個(gè)CALayer
的子類CATextLayer
诅妹,它以圖層的形式包含了UILabel
幾乎所有的繪制特性罚勾,并且額外提供了一些新的特性毅人。
同樣,CATextLayer
也要比UILabel
渲染得快得多尖殃。很少有人知道在iOS 6及之前的版本丈莺,UILabel其實(shí)是通過WebKit來實(shí)現(xiàn)繪制的,這樣就造成了當(dāng)有很多文字的時(shí)候就會(huì)有極大的性能壓力送丰。而CATextLayer
使用了Core text
缔俄,并且渲染得非常快器躏。
讓我們來嘗試用CATextLayer來顯示一些文字
/// CATextLayer
- (void)createCATextLayer {
// create a text layer
CATextLayer *textLayer = [CATextLayer layer];
textLayer.frame = self.contentView.bounds;
[self.contentView.layer addSublayer:textLayer];
// set text attributes
textLayer.foregroundColor = [UIColor blackColor].CGColor;
textLayer.alignmentMode = kCAAlignmentJustified;
textLayer.wrapped = YES;
// set layer font
UIFont *font = [UIFont systemFontOfSize:16];
CFStringRef fontName = (__bridge CFStringRef)font.fontName;
CGFontRef fontRef = CGFontCreateWithFontName(fontName);
textLayer.font = fontName;
textLayer.fontSize = font.pointSize;
CGFontRelease(fontRef);
// choose some text
NSString *text = @"Lorem ipsum dolor sit amet, consectetur adipiscing \ elit. Quisque massa arcu, eleifend vel varius in, facilisis pulvinar \ leo. Nunc quis nunc at mauris pharetra condimentum ut ac neque. Nunc elementum, libero ut porttitor dictum, diam odio congue lacus, vel \ fringilla sapien diam at purus. Etiam suscipit pretium nunc sit amet \ lobortis";
// set layer text
textLayer.string = text;
}
- 運(yùn)行結(jié)果如下
如果你仔細(xì)看這個(gè)文本俐载,你會(huì)發(fā)現(xiàn)一個(gè)奇怪的地方:這些文本有一些像素化
了。這是因?yàn)椴]有以Retina
的方式渲染登失,前面提到了這個(gè)contentScale
屬性遏佣,用來決定圖層內(nèi)容應(yīng)該以怎樣的分辨率來渲染。contentsScale并不關(guān)心屏幕的拉伸因素而總是默認(rèn)為1.0壁畸。如果我們想以Retina的質(zhì)量來顯示文字贼急,我們就得手動(dòng)地設(shè)置CATextLayer的contentsScale屬性,如下:
textLayer.contentsScale = [UIScreen mainScreen].scale;
- 運(yùn)行結(jié)果如下
CATextLayer
的font
屬性不是一個(gè)UIFont類型捏萍,而是一個(gè)CFTypeRef
類型。這樣可以根據(jù)你的具體需要來決定字體屬性應(yīng)該是用CGFontRef
類型還是CTFontRef
類型(Core Text字體)空闲。同時(shí)字體大小也是用fontSize屬性單獨(dú)設(shè)置的令杈,因?yàn)镃TFontRef和CGFontRef并不像UIFont一樣包含點(diǎn)大小。這個(gè)例子會(huì)告訴你如何將UIFont轉(zhuǎn)換成CGFontRef碴倾。
另外逗噩,CATextLayer
的string
屬性并不是你想象的NSString
類型,而是id
類型跌榔。這樣你既可以用NSString也可以用NSAttributedString來指定文本了(注意异雁,NSAttributedString并不是NSString的子類)。屬性化字符串是iOS用來渲染字體風(fēng)格的機(jī)制僧须,它以特定的方式來決定指定范圍內(nèi)的字符串的原始信息纲刀,比如字體,顏色担平,字重示绊,斜體等。
2.1 富文本
iOS 6中暂论,Apple給UILabel和其他UIKit文本視圖添加了直接的屬性化字符串的支持面褐,應(yīng)該說這是一個(gè)很方便的特性。不過事實(shí)上從iOS3.2開始CATextLayer
就已經(jīng)支持屬性化字符串
了取胎。這樣的話展哭,如果你想要支持更低版本的iOS系統(tǒng),CATextLayer
無疑是你向界面中增加富文本的好辦法,而且也不用去跟復(fù)雜的Core Text
打交道匪傍,也省了用UIWebView
的麻煩坝咐。
讓我們編輯一下示例使用到NSAttributedString
。iOS 6及以上我們可以用新的NSTextAttributeName
實(shí)例來設(shè)置我們的字符串屬性析恢,但是練習(xí)的目的是為了演示在iOS 5及以下墨坚,所以我們用了Core Text
,也就是說你需要把Core Text framework
添加到你的項(xiàng)目中映挂。否則泽篮,編譯器是無法識(shí)別屬性常量的。
#import <CoreText/CTFont.h>
#import <CoreText/CTStringAttributes.h>
/// 富文本屬性
- (void)mutableAttrbuteString {
// create a text layer
CATextLayer *textLayer = [CATextLayer layer];
textLayer.frame = self.contentView.bounds;
textLayer.contentsScale = [UIScreen mainScreen].scale;
[self.contentView.layer addSublayer:textLayer];
//set text attributes
textLayer.alignmentMode = kCAAlignmentJustified;
textLayer.wrapped = YES;
UIFont *font = [UIFont systemFontOfSize:15];
NSString *text = @"Lorem ipsum dolor sit amet, consectetur adipiscing \ elit. Quisque massa arcu, eleifend vel varius in, facilisis pulvinar \ leo. Nunc quis nunc at mauris pharetra condimentum ut ac neque. Nunc elementum, libero ut porttitor dictum, diam odio congue lacus, vel \ fringilla sapien diam at purus. Etiam suscipit pretium nunc sit amet \ lobortis";
// create attrbuted string
NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:text];
// convert uifont to a cffont
CFStringRef fontName = (__bridge CFStringRef)font.fontName;
CGFloat fontSize = font.pointSize;
CTFontRef fontRef = CTFontCreateWithName(fontName, fontSize, NULL);
// set text attrbutes
NSDictionary *attribs = @{
(__bridge id)kCTForegroundColorAttributeName:(__bridge id)[UIColor blackColor].CGColor,
(__bridge id)kCTFontAttributeName:(__bridge id)fontRef
};
[string setAttributes:attribs range:NSMakeRange(0, [text length])];
attribs = @{
(__bridge id)kCTForegroundColorAttributeName: (__bridge id)[UIColor redColor].CGColor,
(__bridge id)kCTUnderlineStyleAttributeName: @(kCTUnderlineStyleSingle),
(__bridge id)kCTFontAttributeName: (__bridge id)fontRef
};
[string setAttributes:attribs range:NSMakeRange(6, 5)];
// release the CTFont we created earlier
CFRelease(fontRef);
// set layer text
textLayer.string = string;
}
- 運(yùn)行結(jié)果如下
2.2 行距和字距
有必要提一下的是柑船,由于繪制的實(shí)現(xiàn)機(jī)制不同(Core Text
和WebKit
)帽撑,用CATextLayer
渲染和用UILabel
渲染出的文本行距和字距也不是不盡相同的。
二者的差異程度(由使用的字體和字符決定)總的來說挺小鞍时,但是如果你想正確的顯示普通便簽和CATextLayer就一定要記住這一點(diǎn)亏拉。
2.3 UILabel的替代品
我們已經(jīng)證實(shí)了CATextLayer
比UILabel
有著更好的性能表現(xiàn),同時(shí)還有額外的布局選項(xiàng)并且在iOS 5上支持富文本逆巍。但是與一般的標(biāo)簽比較而言會(huì)更加繁瑣一些及塘。如果我們真的在需求一個(gè)UILabel的可用替代品,最好是能夠在Interface Builder上創(chuàng)建我們的標(biāo)簽锐极,而且盡可能地像一般的視圖一樣正常工作笙僚。
我們應(yīng)該繼承UILabel,然后添加一個(gè)子圖層CATextLayer并重寫顯示文本的方法灵再。但是仍然會(huì)有由UILabel的-drawRect:方法創(chuàng)建的空寄宿圖肋层。而且由于CALayer不支持自動(dòng)縮放和自動(dòng)布局,子視圖并不是主動(dòng)跟蹤視圖邊界的大小翎迁,所以每次視圖大小被更改栋猖,我們不得不手動(dòng)更新子圖層的邊界。
我們真正想要的是一個(gè)用CATextLayer作為宿主圖層的UILabel子類汪榔,這樣就可以隨著視圖自動(dòng)調(diào)整大小而且也沒有冗余的寄宿圖啦蒲拉。
就像我們?cè)诘谝徽?code>圖層樹討論的一樣,每一個(gè)UIView都是寄宿在一個(gè)CALayer的示例上揍异。這個(gè)圖層是由視圖自動(dòng)創(chuàng)建和管理的全陨,那我們可以用別的圖層類型替代它么?一旦被創(chuàng)建衷掷,我們就無法代替這個(gè)圖層了辱姨。但是如果我們繼承了UIView,那我們就可以重寫+layerClass
方法使得在創(chuàng)建的時(shí)候能返回一個(gè)不同的圖層子類戚嗅。UIView會(huì)在初始化的時(shí)候調(diào)用+layerClass方法雨涛,然后用它的返回類型來創(chuàng)建宿主圖層枢舶。
下面演示了一個(gè)UILabel
子類LayerLabel
用CATextLayer
繪制它的問題,而不是調(diào)用一般的UILabel
使用的較慢的-drawRect:
方法替久。LayerLabel示例既可以用代碼實(shí)現(xiàn)凉泄,也可以在Interface Builder實(shí)現(xiàn),只要把普通的標(biāo)簽拖入視圖之中蚯根,然后設(shè)置它的類是LayerLabel就可以了后众。
代碼如下
#import "LayerLabel.h"
@implementation LayerLabel
+ (Class)layerClass {
// this makes our label create a catextlayer
// instead of a regular CALayer for its backing layer
return [CATextLayer class];
}
- (CATextLayer *)textLayer {
return (CATextLayer *)self.layer;
}
- (void)setup {
// set defaults from uilabel settings
self.text = self.text;
self.textColor = self.textColor;
self.font = self.font;
// we should really derive these from the UILabel settings too
// but that's complicated, so for now we'll just hard-code them
[self textLayer].alignmentMode = kCAAlignmentJustified;
[self textLayer].wrapped = YES;
[self.layer display];
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
- (void)awakeFromNib {
// called when creating label using Interface Builder
[self setup];
}
- (void)setText:(NSString *)text {
super.text = text;
// set layer text
[self textLayer].string = text;
}
- (void)setFont:(UIFont *)font {
super.font = font;
// set layer font
CFStringRef fontName = (__bridge CFStringRef)font.fontName;
CGFontRef fontRef = CGFontCreateWithFontName(fontName);
[self textLayer].font = fontRef;
[self textLayer].fontSize = font.pointSize;
CGFontRelease(fontRef);
}
@end
如果你運(yùn)行代碼,你會(huì)發(fā)現(xiàn)文本并沒有像素化颅拦,而我們也沒有設(shè)置contentsScale
屬性蒂誉。把CATextLayer
作為宿主圖層的另一好處就是視圖自動(dòng)設(shè)置了contentsScale
屬性。
在這個(gè)簡(jiǎn)單的例子中距帅,我們只是實(shí)現(xiàn)了UILabel
的一部分風(fēng)格和布局屬性右锨,不過稍微再改進(jìn)一下我們就可以創(chuàng)建一個(gè)支持UILabel
所有功能甚至更多功能的LayerLabel
類(你可以在一些線上的開源項(xiàng)目中找到)。
如果你打算支持iOS 6及以上碌秸,基于CATextLayer
的標(biāo)簽可能就有有些局限性绍移。但是總得來說,如果想在app里面充分利用CALayer
子類讥电,用+layerClass
來創(chuàng)建基于不同圖層的視圖是一個(gè)簡(jiǎn)單可復(fù)用的方法蹂窖。
三 CATransformLayer
當(dāng)我們?cè)跇?gòu)造復(fù)雜的3D事物的時(shí)候,如果能夠組織獨(dú)立元素就太方便了允趟。比如說恼策,你想創(chuàng)造一個(gè)孩子的手臂:你就需要確定哪一部分是孩子的手腕,哪一部分是孩子的前臂潮剪,哪一部分是孩子的肘,哪一部分是孩子的上臂分唾,哪一部分是孩子的肩膀等等抗碰。
當(dāng)然是允許獨(dú)立地移動(dòng)每個(gè)區(qū)域的啦。以肘為指點(diǎn)會(huì)移動(dòng)前臂和手绽乔,而不是肩膀弧蝇。Core Animation
圖層很容易就可以讓你在2D
環(huán)境下做出這樣的層級(jí)體系下的變換,但是3D情況下就不太可能折砸,因?yàn)樗械膱D層都把他的孩子都平面化到一個(gè)場(chǎng)景中(第五章『變換』有提到)看疗。
CATransformLayer
解決了這個(gè)問題,CATransformLayer
不同于普通的CALayer
睦授,因?yàn)樗荒茱@示它自己的內(nèi)容咐旧。只有當(dāng)存在了一個(gè)能作用域子圖層的變換它才真正存在殖演。CATransformLayer
并不平面化它的子圖層,所以它能夠用于構(gòu)造一個(gè)層級(jí)的3D結(jié)構(gòu)饱亿,比如我的手臂示例。
用代碼創(chuàng)建一個(gè)手臂需要相當(dāng)多的代碼懦傍,所以我就演示得更簡(jiǎn)單一些吧:在第五章的立方體示例,我們將通過旋轉(zhuǎn)camara
來解決圖層平面化問題而不是像立方體示例代碼中用的sublayerTransform
。這是一個(gè)非常不錯(cuò)的技巧淑廊,但是只能作用域單個(gè)對(duì)象上,如果你的場(chǎng)景包含兩個(gè)立方體特咆,那我們就不能用這個(gè)技巧單獨(dú)旋轉(zhuǎn)他們了季惩。
那么,就讓我們來試一試CATransformLayer
吧腻格,第一個(gè)問題就來了:在第五章画拾,我們是用多個(gè)視圖來構(gòu)造了我們的立方體荒叶,而不是單獨(dú)的圖層碾阁。我們不能在不打亂已有的視圖層次的前提下在一個(gè)本身不是有寄宿圖的圖層中放置一個(gè)寄宿圖圖層。我們可以創(chuàng)建一個(gè)新的UIView
子類寄宿在CATransformLayer
(用+layerClass
方法)之上些楣。但是脂凶,為了簡(jiǎn)化案例,我們僅僅重建了一個(gè)單獨(dú)的圖層愁茁,而不是使用視圖蚕钦。這意味著我們不能像第五章一樣在立方體表面顯示按鈕和標(biāo)簽,不過我們現(xiàn)在也用不到這個(gè)特性鹅很。
我們以我們?cè)诘谖逭率褂眠^的相同基本邏輯放置立方體嘶居。但是并不像以前那樣直接將立方面添加到容器視圖的宿主圖層,我們將他們放置到一個(gè)CATransformLayer
中創(chuàng)建一個(gè)獨(dú)立的立方體對(duì)象促煮,然后將兩個(gè)這樣的立方體放進(jìn)容器中邮屁。我們隨機(jī)地給立方面染色以將他們區(qū)分開來,這樣就不用靠標(biāo)簽或是光亮來區(qū)分他們菠齿。
/// CATransformLayer
- (void)createTransformLayer {
// setup the perspective transform
CATransform3D pt = CATransform3DIdentity;
pt.m34 = -1.0 / 500.0;
self.contentView.layer.sublayerTransform = pt;
// setup the transform for cube 1 and add it
CATransform3D c1t = CATransform3DIdentity;
c1t = CATransform3DTranslate(c1t, -100, 0, 0);
CALayer *cube1 = [self cubeWithTransform:c1t];
[self.contentView.layer addSublayer:cube1];
// setup the transform for cube2 and add it
CATransform3D c2t = CATransform3DIdentity;
c2t = CATransform3DTranslate(c2t, 100, 0, 0);
c2t = CATransform3DRotate(c2t, -M_PI_4, 1, 0, 0);
c2t = CATransform3DRotate(c2t, -M_PI_4, 0, 1, 0);
CALayer *cube2 = [self cubeWithTransform:c2t];
[self.contentView.layer addSublayer:cube2];
}
- (CALayer *)faceWithTransform:(CATransform3D)transform {
// create cube face layer
CALayer *face = [CALayer layer];
face.frame = CGRectMake(-50, -50, 100, 100);
// apply a random color
CGFloat red = (rand() / (double)INT_MAX);
CGFloat green = (rand() / (double)INT_MAX);
CGFloat blue = (rand() / (double)INT_MAX);
face.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
// apply the transform and return
face.transform = transform;
return face;
}
- (CALayer *)cubeWithTransform:(CATransform3D)transform {
// create cube layer
CATransformLayer *cube = [CATransformLayer layer];
// add cube face 1
CATransform3D ct = CATransform3DMakeTranslation(0, 0, 50);
[cube addSublayer:[self faceWithTransform:ct]];
//add cube face 2
ct = CATransform3DMakeTranslation(50, 0, 0);
ct = CATransform3DRotate(ct, M_PI_2, 0, 1, 0);
[cube addSublayer:[self faceWithTransform:ct]];
//add cube face 3
ct = CATransform3DMakeTranslation(0, -50, 0);
ct = CATransform3DRotate(ct, M_PI_2, 1, 0, 0);
[cube addSublayer:[self faceWithTransform:ct]];
//add cube face 4
ct = CATransform3DMakeTranslation(0, 50, 0);
ct = CATransform3DRotate(ct, -M_PI_2, 1, 0, 0);
[cube addSublayer:[self faceWithTransform:ct]];
//add cube face 5
ct = CATransform3DMakeTranslation(-50, 0, 0);
ct = CATransform3DRotate(ct, -M_PI_2, 0, 1, 0);
[cube addSublayer:[self faceWithTransform:ct]];
//add cube face 6
ct = CATransform3DMakeTranslation(0, 0, -50);
ct = CATransform3DRotate(ct, M_PI, 0, 1, 0);
[cube addSublayer:[self faceWithTransform:ct]];
// center the cube layer within the container
CGSize containerSize = self.contentView.bounds.size;
cube.position = CGPointMake(containerSize.width * 0.5, containerSize.height * 0.5);
// apply the transform
cube.transform = transform;
return cube;
}
- 運(yùn)行結(jié)果如下
四 CAGradientLayer
CAGradientLayer
是用來生成兩種或更多顏色平滑漸變
的佑吝。用Core Graphics
復(fù)制一個(gè)CAGradientLayer
并將內(nèi)容繪制到一個(gè)普通圖層的寄宿圖也是有可能的,但是CAGradientLayer
的真正好處在于繪制使用了硬件加速绳匀。
CAGradientLayer
也有startPoint
和endPoint
屬性芋忿,他們決定了漸變的方向
。這兩個(gè)參數(shù)是以單位坐標(biāo)系進(jìn)行的定義疾棵,所以左上角坐標(biāo)是{0, 0}戈钢,右下角坐標(biāo)是{1, 1}
簡(jiǎn)單的兩種顏色的對(duì)角線漸變
/// 顏色漸變
- (void)createGradientLayer {
// create gradient layer and add it to our container view
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = self.contentView.bounds;
[self.contentView.layer addSublayer:gradientLayer];
// set gradient colors
gradientLayer.colors = @[(__bridge id)[UIColor redColor].CGColor,
(__bridge id)[UIColor blueColor].CGColor];
// set gradient start and end points
gradientLayer.startPoint = CGPointMake(0, 0);
gradientLayer.endPoint = CGPointMake(1, 1);
}
- 運(yùn)行結(jié)果如下
4.2 多重漸變
如果你愿意,colors
屬性可以包含很多顏色是尔,所以創(chuàng)建一個(gè)彩虹一樣的多重漸變也是很簡(jiǎn)單的殉了。默認(rèn)情況下,這些顏色在空間上均勻地被渲染嗜历,但是我們可以用locations
屬性來調(diào)整空間宣渗。locations屬性是一個(gè)浮點(diǎn)數(shù)值的數(shù)組(以NSNumber包裝)抖所。這些浮點(diǎn)數(shù)定義了colors屬性中每個(gè)不同顏色的位置,同樣的痕囱,也是以單位坐標(biāo)系進(jìn)行標(biāo)定田轧。0.0代表著漸變的開始,1.0代表著結(jié)束鞍恢。
locations
數(shù)組并不是強(qiáng)制要求的傻粘,但是如果你給它賦值了就一定要確保locations的數(shù)組大小和colors數(shù)組大小一定要相同,否則你將會(huì)得到一個(gè)空白的漸變帮掉。
下面代碼現(xiàn)在變成了從紅到黃最后到綠色的漸變弦悉。locations數(shù)組指定了0.0,0.25和0.5三個(gè)數(shù)值蟆炊,這樣這三個(gè)漸變就有點(diǎn)像擠在了左上角稽莉。
/// 多重顏色漸變
- (void)createMultiGradientLayer {
// create gradient layer and add it to our container view
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = self.contentView.bounds;
[self.contentView.layer addSublayer:gradientLayer];
// set gradient colors
gradientLayer.colors = @[(__bridge id)[UIColor redColor].CGColor,
(__bridge id)[UIColor yellowColor].CGColor,
(__bridge id)[UIColor greenColor].CGColor];
// set locations
gradientLayer.locations = @[@0.0, @0.25, @0.5];
// set gradient start and end points
gradientLayer.startPoint = CGPointMake(0, 0);
gradientLayer.endPoint = CGPointMake(1, 1);
}
五 CAReplicatorLayer
CAReplicatorLayer
的目的是為了高效生成許多相似的圖層。它會(huì)繪制一個(gè)或多個(gè)圖層的子圖層涩搓,并在每個(gè)復(fù)制體上應(yīng)用不同的變換污秆。看上去演示能夠更加解釋這些昧甘,我們來寫個(gè)例子吧。
5.1 重復(fù)圖層(Repeating Layers)
我們?cè)谄聊坏闹虚g創(chuàng)建了一個(gè)小白色方塊圖層庸推,然后用CAReplicatorLayer
生成十個(gè)圖層組成一個(gè)圓圈贬媒。instanceCount
屬性指定了圖層需要重復(fù)多少次肘习。instanceTransform
指定了一個(gè)CATransform3D
后3D變換(這種情況下井厌,下一圖層的位移和旋轉(zhuǎn)將會(huì)移動(dòng)到圓圈的下一個(gè)點(diǎn))仅仆。
變換是逐步增加的,每個(gè)實(shí)例都是相對(duì)于前一實(shí)例布局垢袱。這就是為什么這些復(fù)制體最終不會(huì)出現(xiàn)在同意位置上墓拜,圖6.8是代碼運(yùn)行結(jié)果。
/// 5.重復(fù)圖層(Repeating Layers)
- (void)createReplicatorLayer {
// create a replicator layer and add it to our view
CAReplicatorLayer *replicator = [CAReplicatorLayer layer];
replicator.frame = self.contentView.bounds;
[self.contentView.layer addSublayer:replicator];
// configure the replicator
replicator.instanceCount = 10;
// apply a transform for each instance
CATransform3D transform = CATransform3DIdentity;
transform = CATransform3DTranslate(transform, 0, 200, 0);
transform = CATransform3DRotate(transform, M_PI / 5.0, 0, 0, 1);
transform = CATransform3DTranslate(transform, 0, -200, 0);
replicator.instanceTransform = transform;
// apply a color shift for each instance
replicator.instanceBlueOffset = -0.1;
replicator.instanceGreenOffset = -0.1;
// create a sublayer and place it inside the replicator
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(100.0f, 100.0f, 100.0f, 100.0f);
layer.backgroundColor = [UIColor whiteColor].CGColor;
[replicator addSublayer:layer];
}
- 運(yùn)行結(jié)果如下
注意到當(dāng)圖層在重復(fù)的時(shí)候请契,他們的顏色也在變化:這是用instanceBlueOffset
和instanceGreenOffset
屬性實(shí)現(xiàn)的咳榜。通過逐步減少藍(lán)色和綠色通道夏醉,我們逐漸將圖層顏色轉(zhuǎn)換成了紅色。這個(gè)復(fù)制效果看起來很酷涌韩,但是CAReplicatorLayer
真正應(yīng)用到實(shí)際程序上的場(chǎng)景比如:一個(gè)游戲中導(dǎo)彈的軌跡云畔柔,或者粒子爆炸(盡管iOS 5已經(jīng)引入了CAEmitterLayer,它更適合創(chuàng)建任意的粒子效果)臣樱。除此之外靶擦,還有一個(gè)實(shí)際應(yīng)用是:反射。
5.2 反射
使用CAReplicatorLayer
并應(yīng)用一個(gè)負(fù)比例變換于一個(gè)復(fù)制圖層雇毫,你就可以創(chuàng)建指定視圖(或整個(gè)視圖層次)內(nèi)容的鏡像圖片玄捕,這樣就創(chuàng)建了一個(gè)實(shí)時(shí)的反射
效果。讓我們來嘗試實(shí)現(xiàn)這個(gè)創(chuàng)意:指定一個(gè)繼承于UIView的ReflectionView棚放,它會(huì)自動(dòng)產(chǎn)生內(nèi)容的反射效果枚粘。實(shí)現(xiàn)這個(gè)效果的代碼很簡(jiǎn)單,實(shí)際上用ReflectionView
實(shí)現(xiàn)這個(gè)效果會(huì)更簡(jiǎn)單柬姚,我們只需生成ReflectionView
的實(shí)例,它就會(huì)實(shí)時(shí)生成子視圖的反射撕捍。
#import "ReflectionView.h"
@implementation ReflectionView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
- (void)awakeFromNib {
[self setup];
}
+ (Class)layerClass {
return [CAReplicatorLayer class];
}
- (void)setup {
CAReplicatorLayer *layer = (CAReplicatorLayer *)self.layer;
layer.instanceCount = 2;
// move reflection instance below original and flip vertically
CATransform3D transform = CATransform3DIdentity;
CGFloat verticalOffset = self.bounds.size.height * 0.5;
transform = CATransform3DTranslate(transform, 0, verticalOffset, 0);
transform = CATransform3DScale(transform, 1, -1, 0);
layer.instanceTransform = transform;
//reduce alpha of reflection layer
replicatorLayer.instanceAlphaOffset = -0.6;
UIImage *image = [UIImage imageNamed:@"cat"];
CALayer *imageLayer = [CALayer layer];
[imageLayer setContentsScale:[[UIScreen mainScreen] scale]];
[imageLayer setContents:(__bridge id)[image CGImage]];
[imageLayer setBounds:CGRectMake(0.0, 0.0, [image size].width, [image size].height)];
[imageLayer setAnchorPoint:CGPointMake(0.0, 0.0)];
[replicatorLayer addSublayer:imageLayer];
}
@end
- 運(yùn)行結(jié)果如下
六 CAScrollLayer
對(duì)于一個(gè)未轉(zhuǎn)換的圖層狮腿,它的bounds和它的frame是一樣的,frame屬性是由bounds屬性自動(dòng)計(jì)算而出的贴硫,所以更改任意一個(gè)值都會(huì)更新其他值。
但是如果你只想顯示一個(gè)大圖層里面的一小部分呢汁尺。比如說,你可能有一個(gè)很大的圖片苞也,你希望用戶能夠隨意滑動(dòng),或者是一個(gè)數(shù)據(jù)或文本的長列表殷勘。在一個(gè)典型的iOS應(yīng)用中,你可能會(huì)用到UITableView或是UIScrollView贤斜,但是對(duì)于獨(dú)立的圖層來說,什么會(huì)等價(jià)于剛剛提到的UITableView和UIScrollView呢?
在第二章中阳堕,我們探索了圖層的contentsRect
屬性的用法,它的確是能夠解決在圖層中小地方顯示大圖片的解決方法。但是如果你的圖層包含子圖層那它就不是一個(gè)非常好的解決方案道偷,因?yàn)椴⑽。@樣做的話每次你想滑動(dòng)
可視區(qū)域的時(shí)候,你就需要手工重新計(jì)算并更新所有的子圖層位置剃执。
這個(gè)時(shí)候就需要CAScrollLayer
了。CAScrollLayer有一個(gè)-scrollToPoint:
方法,它自動(dòng)適應(yīng)bounds
的原點(diǎn)以便圖層內(nèi)容出現(xiàn)在滑動(dòng)的地方遣耍。注意,這就是它做的所有事情棋傍。前面提到過,Core Animation
并不處理用戶輸入麸拄,所以CAScrollLayer
并不負(fù)責(zé)將觸摸事件轉(zhuǎn)換為滑動(dòng)事件,既不渲染滾動(dòng)條,也不實(shí)現(xiàn)任何iOS指定行為例如滑動(dòng)反彈(當(dāng)視圖滑動(dòng)超多了它的邊界的將會(huì)反彈回正確的地方)主穗。
讓我們來用CAScrollLayer
來常見一個(gè)基本的UIScrollView
替代品争拐。我們將會(huì)用CAScrollLayer
作為視圖的宿主圖層,并創(chuàng)建一個(gè)自定義的UIView绑雄,然后用UIPanGestureRecognizer實(shí)現(xiàn)觸摸事件響應(yīng)。
@implementation ScrollView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
- (void)awakeFromNib {
[self setup];
}
+ (Class)layerClass {
return [CAScrollLayer class];
}
- (void)setup {
// enable clipping
self.layer.masksToBounds = YES;
// attach pan gesture recongnizer
UIPanGestureRecognizer *recognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
[self addGestureRecognizer:recognizer];
UIImage *image = [UIImage imageNamed:@"cat"];
CALayer *imageLayer = [CALayer layer];
[imageLayer setContentsScale:[[UIScreen mainScreen] scale]];
[imageLayer setContents:(__bridge id)[image CGImage]];
[imageLayer setBounds:CGRectMake(0.0, 0.0, [image size].width, [image size].height)];
[imageLayer setAnchorPoint:CGPointMake(0.0, 0.0)];
[(CAScrollLayer *)self.layer addSublayer:imageLayer];
}
- (void)pan:(UIPanGestureRecognizer *)recognizer {
//get the offset by subtracting the pan gesture
//translation from the current bounds origin
CGPoint offset = self.bounds.origin;
offset.x -= [recognizer translationInView:self].x;
offset.y -= [recognizer translationInView:self].y;
// scroll the layer
[(CAScrollLayer *)self.layer scrollToPoint:offset];
// reset the pan gesture translation
[recognizer setTranslation:CGPointZero inView:self];
}
@end
- 運(yùn)行結(jié)果如下
不同于UIScrollView已脓,我們定制的滑動(dòng)視圖類并沒有實(shí)現(xiàn)任何形式的邊界檢查(bounds checking)
厕宗。圖層內(nèi)容極有可能滑出視圖的邊界并無限滑下去。CAScrollLayer
并沒有等同于UIScrollView
中contentSize
的屬性,所以當(dāng)CAScrollLayer滑動(dòng)的時(shí)候完全沒有一個(gè)全局的可滑動(dòng)區(qū)域的概念膜楷,也無法自適應(yīng)它的邊界原點(diǎn)至你指定的值。它之所以不能自適應(yīng)邊界大小是因?yàn)樗恍枰卦福瑑?nèi)容完全可以超過邊界宗收。
那你一定會(huì)奇怪用CAScrollLayer
的意義到底何在采驻,因?yàn)槟憧梢院?jiǎn)單地用一個(gè)普通的CALayer然后手動(dòng)適應(yīng)邊界原點(diǎn)啊膳叨。真相其實(shí)并不復(fù)雜,UIScrollView
并沒有用CAScrollLayer
龄坪,事實(shí)上,就是簡(jiǎn)單的通過直接操作圖層邊界來實(shí)現(xiàn)滑動(dòng)妓局。
CAScrollLayer有一個(gè)潛在的有用特性好爬。如果你查看CAScrollLayer的頭文件型豁,你就會(huì)注意到有一個(gè)擴(kuò)展分類實(shí)現(xiàn)了一些方法和屬性:
- (void)scrollPoint:(CGPoint)p;
- (void)scrollRectToVisible:(CGRect)r;
@property(readonly) CGRect visibleRect;
看到這些方法和屬性名充尉,你也許會(huì)以為這些方法給每個(gè)CALayer實(shí)例增加了滑動(dòng)功能姿鸿。但是事實(shí)上他們只是放置在CAScrollLayer中的圖層的實(shí)用方法。
scrollPoint:
方法從圖層樹中查找并找到第一個(gè)可用的CAScrollLayer
热某,然后滑動(dòng)它使得指定點(diǎn)成為可視的。scrollRectToVisible:
方法實(shí)現(xiàn)了同樣的事情只不過是作用在一個(gè)矩形上的秘遏。visibleRect屬性決定圖層(如果存在的話)的哪部分是當(dāng)前的可視區(qū)域。如果你自己實(shí)現(xiàn)這些方法就會(huì)相對(duì)容易明白一點(diǎn),但是CAScrollLayer幫你省了這些麻煩审丘,所以當(dāng)涉及到實(shí)現(xiàn)圖層滑動(dòng)的時(shí)候就可以用上了。
七 CATiledLayer
有些時(shí)候你可能需要繪制一個(gè)很大的圖片脓钾,常見的例子就是一個(gè)高像素的照片或者是地球表面的詳細(xì)地圖。iOS應(yīng)用通暢運(yùn)行在內(nèi)存受限的設(shè)備上握截,所以讀取整個(gè)圖片到內(nèi)存中是不明智的。載入大圖可能會(huì)相當(dāng)?shù)芈枧切?duì)你看上去比較方便的做法(在主線程調(diào)用UIImage的-imageNamed:方法或者-imageWithContentsOfFile:方法)將會(huì)阻塞你的用戶界面蒲讯,至少會(huì)引起動(dòng)畫卡頓現(xiàn)象。
能高效繪制在iOS上的圖片也有一個(gè)大小限制。所有顯示在屏幕上的圖片最終都會(huì)被轉(zhuǎn)化為OpenGL紋理
,同時(shí)OpenGL有一個(gè)最大的紋理尺寸(通常是20482048独郎,或40964096贪婉,這個(gè)取決于設(shè)備型號(hào))莫湘。如果你想在單個(gè)紋理中顯示一個(gè)比這大的圖腰池,即便圖片已經(jīng)存在于內(nèi)存中了,你仍然會(huì)遇到很大的性能問題,因?yàn)?code>Core Animation強(qiáng)制用CPU
處理圖片而不是更快的GPU
后面會(huì)更加詳細(xì)地解釋了軟件繪制和硬件繪制)拍皮。
CATiledLayer
為載入大圖造成的性能問題提供了一個(gè)解決方案:將大圖分解成小片然后將他們單獨(dú)按需載入咆耿。讓我們用實(shí)驗(yàn)來證明一下。
7.1 小片裁剪
我們將會(huì)從一個(gè)2048*2048分辨率的雪人圖片入手。為了能夠從CATiledLayer中獲益,我們需要把這個(gè)圖片裁切成許多小一些的圖片艾帐。你可以通過代碼來完成這件事情,但是如果你在運(yùn)行時(shí)讀入整個(gè)圖片并裁切,那CATiledLayer這些所有的性能優(yōu)點(diǎn)就損失殆盡了。理想情況下來說腥泥,最好能夠逐個(gè)步驟來實(shí)現(xiàn)夹厌。
演示了一個(gè)簡(jiǎn)單的Mac OS命令行程序裆悄,它用CATiledLayer將一個(gè)圖片裁剪成小圖并存儲(chǔ)到不同的文件中矛纹。
int main(int argc, const char * argv[]) {
@autoreleasepool {
// handle incorrect arguments
if (argc < 2) {
NSLog(@"TileCutter arguments: inputfile");
return 0;
}
// input file
NSString *inputFile = [NSString stringWithCString:argv[1] encoding:NSUTF8StringEncoding];
// tile size
CGFloat tileSize = 256; // output path
NSString *outputPath = [inputFile stringByDeletingPathExtension];
// load image
NSImage *image = [[NSImage alloc] initWithContentsOfFile:inputFile];
NSSize size = [image size];
NSArray *representations = [image representations];
if ([representations count]) {
NSBitmapImageRep *representation = representations[0];
size.width = [representation pixelsWide];
size.height = [representation pixelsHigh];
}
NSRect rect = NSMakeRect(0, 0, size.width, size.height);
CGImageRef imageRef = [image CGImageForProposedRect:&rect context:NULL hints:nil];
// calculate rows and columns
NSInteger rows = ceil(size.height / tileSize);
NSInteger cols = ceil(size.width / tileSize);
// generate tiles
for (int y = 0; y < rows; ++y) {
for (int x = 0; x < cols; ++x) {
// extract tile image
CGRect tileRect = CGRectMake(x * tileSize, y * tileSize, tileSize, tileSize);
CGImageRef tileImage = CGImageCreateWithImageInRect(imageRef, tileRect);
// convert to jpeg data
NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithCGImage:tileImage];
NSData *data = [imageRep representationUsingType:NSJPEGFileType properties:nil];
CGImageRelease(tileImage);
// save file
NSString *path = [outputPath stringByAppendingFormat: @"_%02i_%02i.jpg", x, y];
[data writeToFile:path atomically:NO];
}
}
}
return NSApplicationMain(argc, argv);
}
這個(gè)程序?qū)?code>2048*2048分辨率的圖案裁剪成了64
個(gè)不同的256*256
的小圖。(256*256是CATiledLayer的默認(rèn)小圖大小光稼,默認(rèn)大小可以通過tileSize屬性更改)或南。程序接受一個(gè)圖片路徑作為命令行的第一個(gè)參數(shù)孩等。我們可以在編譯的scheme將路徑參數(shù)硬編碼然后就可以在Xcode中運(yùn)行了权她,但是以后作用在另一個(gè)圖片上就不方便了虏肾。所以藻雌,我們編譯了這個(gè)程序并把它保存到敏感的地方滚局,然后從終端調(diào)用,如下面所示:
> path/to/TileCutterApp path/to/Snowman.jpg
這個(gè)程序相當(dāng)基礎(chǔ)略贮,但是能夠輕易地?cái)U(kuò)展支持額外的參數(shù)比如小圖大小拄丰,或者導(dǎo)出格式等等。運(yùn)行結(jié)果是64個(gè)新圖的序列牡整,如下面命名:
Snowman_00_00.jpg
Snowman_00_01.jpg
Snowman_00_02.jpg
...
Snowman_07_07.jpg
既然我們有了裁切后的小圖,我們就要讓iOS程序用到他們昼弟。CATiledLayer
很好地和UIScrollView
集成在一起旬盯。除了設(shè)置圖層和滑動(dòng)視圖邊界以適配整個(gè)圖片大小,我們真正要做的就是實(shí)現(xiàn)-drawLayer:inContext:
方法员萍,當(dāng)需要載入新的小圖時(shí),CATiledLayer就會(huì)調(diào)用到這個(gè)方法极景。
一個(gè)簡(jiǎn)單的滾動(dòng)CATiledLayer實(shí)現(xiàn)
/// 7 CATiledLayer
- (void)createTiledLayer {
// create scrollView
self.scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 300, 300)];
self.scrollView.center = self.view.center;
[self.view addSubview:self.scrollView];
//add the tiled layer
CATiledLayer *tileLayer = [CATiledLayer layer];
tileLayer.frame = CGRectMake(0, 0, 2048, 2048);
tileLayer.delegate = self;
[self.scrollView.layer addSublayer:tileLayer];
//configure the scroll view
self.scrollView.contentSize = tileLayer.frame.size;
//draw layer
[tileLayer setNeedsDisplay];
}
- (void)drawLayer:(CATiledLayer *)layer inContext:(CGContextRef)ctx {
// determine tile coordinate
CGRect bounds = CGContextGetClipBoundingBox(ctx);
NSInteger x = floor(bounds.origin.x / layer.tileSize.width);
NSInteger y = floor(bounds.origin.y / layer.tileSize.height);
// load tile image
NSString *imageName = [NSString stringWithFormat: @"Snowman_%02li_%02li", (long)x, (long)y];
NSString *imagePath = [[NSBundle mainBundle] pathForResource:imageName ofType:@"jpg"];
UIImage *tileImage = [UIImage imageWithContentsOfFile:imagePath];
//draw tile
UIGraphicsPushContext(ctx);
[tileImage drawInRect:bounds];
UIGraphicsPopContext();
}
當(dāng)你滑動(dòng)這個(gè)圖片与境,你會(huì)發(fā)現(xiàn)當(dāng)CATiledLayer
載入小圖的時(shí)候,他們會(huì)淡入到界面中家坎。這是CATiledLayer的默認(rèn)行為。(你可能已經(jīng)在iOS 6之前的蘋果地圖程序中見過這個(gè)效果)你可以用fadeDuration
屬性改變淡入時(shí)長或直接禁用掉绩衷。CATiledLayer(不同于大部分的UIKit和Core Animation方法)支持多線程繪制蜕衡,-drawLayer:inContext:方法可以在多個(gè)線程中同時(shí)地并發(fā)調(diào)用,所以請(qǐng)小心謹(jǐn)慎地確保你在這個(gè)方法中實(shí)現(xiàn)的繪制代碼是線程安全的悴侵。
7.2 Retina小圖
你也許已經(jīng)注意到了這些小圖并不是以Retina
的分辨率顯示的。為了以屏幕的原生分辨率來渲染CATiledLayer
,我們需要設(shè)置圖層的contentsScale
來匹配UIScreen的scale屬性:
tileLayer.contentsScale = [UIScreen mainScreen].scale;
有趣的是厢岂,tileSize
是以像素為單位馍管,而不是點(diǎn)谊路,所以增大了contentsScale
就自動(dòng)有了默認(rèn)的小圖尺寸(現(xiàn)在它是128128的點(diǎn)而不是256256).所以,我們不需要手工更新小圖的尺寸或是在Retina分辨率下指定一個(gè)不同的小圖根悼。我們需要做的是適應(yīng)小圖渲染代碼以對(duì)應(yīng)安排scale的變化凶异,然而:
//determine tile coordinate
CGRect bounds = CGContextGetClipBoundingBox(ctx);
CGFloat scale = [UIScreen mainScreen].scale;
NSInteger x = floor(bounds.origin.x / layer.tileSize.width * scale);
NSInteger y = floor(bounds.origin.y / layer.tileSize.height * scale);
通過這個(gè)方法糾正scale
也意味著我們的雪人圖將以一半的大小渲染在Retina設(shè)備上(總尺寸是10241024,而不是20482048)挤巡。這個(gè)通常都不會(huì)影響到用CATiledLayer
正常顯示的圖片類型(比如照片和地圖剩彬,他們?cè)谠O(shè)計(jì)上就是要支持放大縮小,能夠在不同的縮放條件下顯示)矿卑,但是也需要在心里明白喉恋。
八 CAEmitterLayer
在iOS 5中,蘋果引入了一個(gè)新的CALayer
子類叫做CAEmitterLayer
母廷。CAEmitterLayer是一個(gè)高性能的粒子引擎
轻黑,被用來創(chuàng)建實(shí)時(shí)例子動(dòng)畫如:煙霧,火琴昆,雨等等這些效果氓鄙。
CAEmitterLayer
看上去像是許多CAEmitterCell
的容器,這些CAEmitierCell
定義了一個(gè)例子效果
业舍。你將會(huì)為不同的例子效果定義一個(gè)或多個(gè)CAEmitterCell
作為模版抖拦,同時(shí)CAEmitterLayer負(fù)責(zé)基于這些模版實(shí)例化一個(gè)粒子流。一個(gè)CAEmitterCell類似于一個(gè)CALayer
:它有一個(gè)contents屬性可以定義為一個(gè)CGImage舷暮,另外還有一些可設(shè)置屬性控制著表現(xiàn)和行為态罪。我們不會(huì)對(duì)這些屬性逐一進(jìn)行詳細(xì)的描述,你們可以在CAEmitterCell類的頭文件中找到下面。
/// 8 CAEmitterLayer 粒子效果
- (void)createCAEmitterLayer {
// create particle emitter layer
CAEmitterLayer *emitter = [CAEmitterLayer layer];
emitter.frame = self.contentView.bounds;
[self.contentView.layer addSublayer:emitter];
// configure emitter
emitter.renderMode = kCAEmitterLayerAdditive;
emitter.emitterPosition = CGPointMake(emitter.frame.size.width * 0.5, emitter.frame.size.height * 0.5);
// create a particle template
CAEmitterCell *cell = [[CAEmitterCell alloc] init];
cell.contents = (__bridge id)[UIImage imageNamed:@"red"].CGImage;
cell.birthRate = 150;
cell.lifetime = 5.0;
cell.color = [UIColor colorWithRed:1 green:0.5 blue:0.1 alpha:1.0].CGColor;
cell.alphaSpeed = -0.4;
cell.velocity = 50;
cell.velocityRange = 50;
cell.emissionRange = M_PI * 2.0;
// add particle template to emitter
emitter.emitterCells = @[cell];
}
- 運(yùn)行效果如下
CAEMitterCell的屬性基本上可以分為三種:
這種粒子的某一屬性的初始值复颈。比如,color屬性指定了一個(gè)可以混合圖片內(nèi)容顏色的混合色沥割。在示例中耗啦,我們將它設(shè)置為桔色。
例子某一屬性的變化范圍驯遇。比如
emissionRange
屬性的值是2π芹彬,這意味著例子可以從360度任意位置反射出來。如果指定一個(gè)小一些的值叉庐,就可以創(chuàng)造出一個(gè)圓錐形指定值在時(shí)間線上的變化舒帮。比如,在示例中陡叠,我們將
alphaSpeed
設(shè)置為-0.4玩郊,就是說例子的透明度每過一秒就是減少0.4,這樣就有發(fā)射出去之后逐漸小時(shí)的效果枉阵。
CAEmitterLayer
的屬性它自己控制著整個(gè)例子系統(tǒng)的位置和形狀译红。一些屬性比如birthRate
,lifetime
和celocity
兴溜,這些屬性在CAEmitterCell
中也有侦厚。這些屬性會(huì)以相乘的方式作用在一起耻陕,這樣你就可以用一個(gè)值來加速或者擴(kuò)大整個(gè)例子系統(tǒng)。其他值得提到的屬性有以下這些:
preservesDepth
刨沦,是否將3D例子系統(tǒng)平面化到一個(gè)圖層(默認(rèn)值)或者可以在3D空間中混合其他的圖層renderMode
诗宣,控制著在視覺上粒子圖片是如何混合的。你可能已經(jīng)注意到了示例中我們把它設(shè)置為kCAEmitterLayerAdditive想诅,它實(shí)現(xiàn)了這樣一個(gè)效果:合并例子重疊部分的亮度使得看上去更亮召庞。如果我們把它設(shè)置為默認(rèn)的kCAEmitterLayerUnordered,效果就沒那么好看了
emitter.renderMode = kCAEmitterLayerUnordered;
- 運(yùn)行效果如下
九 CAEAGLLayer
當(dāng)iOS要處理高性能
圖形繪制来破,必要時(shí)就是OpenGL
篮灼。應(yīng)該說它應(yīng)該是最后的殺手锏,至少對(duì)于非游戲的應(yīng)用來說是的徘禁。因?yàn)橄啾?code>Core Animation和UIkit
框架诅诱,它不可思議地復(fù)雜。
OpenGL
提供了Core Animation
的基礎(chǔ)送朱,它是底層的C接口逢艘,直接和iPhone,iPad的硬件通信骤菠,極少地抽象出來的方法它改。OpenGL沒有對(duì)象或是圖層的繼承概念。它只是簡(jiǎn)單地處理三角形商乎。OpenGL中所有東西都是3D空間中有顏色和紋理的三角形央拖。用起來非常復(fù)雜和強(qiáng)大,但是用OpenGL繪制iOS用戶界面就需要很多很多的工作了鹉戚。
為了能夠以高性能使用Core Animation
鲜戒,你需要判斷你需要繪制哪種內(nèi)容(矢量圖形,例子抹凳,文本遏餐,等等),但后選擇合適的圖層去呈現(xiàn)這些內(nèi)容赢底,Core Animation中只有一些類型的內(nèi)容是被高度優(yōu)化的失都;所以如果你想繪制的東西并不能找到標(biāo)準(zhǔn)的圖層類,想要得到高性能就比較費(fèi)事情了幸冻。
因?yàn)?code>OpenGL根本不會(huì)對(duì)你的內(nèi)容進(jìn)行假設(shè)粹庞,它能夠繪制得相當(dāng)快。利用OpenGL洽损,你可以繪制任何你知道必要的集合信息和形狀邏輯的內(nèi)容庞溜。所以很多游戲都喜歡用OpenGL(這些情況下,Core Animation的限制就明顯了:它優(yōu)化過的內(nèi)容類型并不一定能滿足需求)碑定,但是這樣依賴流码,方便的高度抽象接口就沒了又官。
在iOS 5中,蘋果引入了一個(gè)新的框架叫做GLKit
漫试,它去掉了一些設(shè)置OpenGL
的復(fù)雜性赏胚,提供了一個(gè)叫做CLKView
的UIView的子類,幫你處理大部分的設(shè)置和繪制工作商虐。前提是各種各樣的OpenGL繪圖緩沖的底層可配置項(xiàng)仍然需要你用CAEAGLLayer
完成,它是CALayer的一個(gè)子類崖疤,用來顯示任意的OpenGL圖形秘车。
大部分情況下你都不需要手動(dòng)設(shè)置CAEAGLLayer
(假設(shè)用GLKView),過去的日子就不要再提了劫哼。特別的叮趴,我們將設(shè)置一個(gè)OpenGL ES 2.0的上下文,它是現(xiàn)代的iOS設(shè)備的標(biāo)準(zhǔn)做法权烧。
盡管不需要GLKit
也可以做到這一切眯亦,但是GLKit囊括了很多額外的工作,比如設(shè)置頂點(diǎn)和片段著色器般码,這些都以類C語言叫做GLSL自包含在程序中妻率,同時(shí)在運(yùn)行時(shí)載入到圖形硬件中。編寫GLSL
代碼和設(shè)置EAGLayer
沒有什么關(guān)系板祝,所以我們將用GLKBaseEffect類將著色邏輯抽象出來宫静。其他的事情,我們還是會(huì)有以往的方式券时。
在開始之前孤里,你需要將GLKit
和OpenGLES
框架加入到你的項(xiàng)目中,然后就可以實(shí)現(xiàn)了橘洞,里面是設(shè)置一個(gè)GAEAGLLayer的最少工作捌袜,它使用了OpenGL ES 2.0 的繪圖上下文,并渲染了一個(gè)有色三角炸枣。
#import <GLKit/GLKit.h>
#import <OpenGLES/OpenGLESAvailability.h>
#pragma mark - CAEAGLLayer 相關(guān)變量
/** glView */
@property(nonatomic, strong)UIView *glView;
/** glContext */
@property(nonatomic, strong)EAGLContext *glContext;
/** glLayer */
@property(nonatomic, strong)CAEAGLLayer *glLayer;
/** frameBuffer */
@property(nonatomic, assign)GLuint frameBuffer;
/** colorRenderbuffer */
@property(nonatomic, assign)GLuint colorRenderbuffer;
/** framebufferWidth */
@property(nonatomic, assign)GLuint frameBufferWidth;
/** framebufferHeight */
@property(nonatomic, assign)GLuint frameBufferHeight;
/** effect */
@property(nonatomic, strong)GLKBaseEffect *effect;
/// 9.CAEAGLLayer
- (void)createEAGLayer {
// setup context
self.glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
[EAGLContext setCurrentContext:self.glContext];
self.glView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
self.glView.center = self.view.center;
[self.view addSubview:self.glView];
// setup layer
self.glLayer = [CAEAGLLayer layer];
self.glLayer.frame = self.glView.bounds;
[self.glView.layer addSublayer:self.glLayer];
self.glLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking:@NO, kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8};
// setup base effect
self.effect = [[GLKBaseEffect alloc] init];
// setup buffers
[self setUpBuffers];
// draw frame
[self drawFrame];
}
- (void)viewDidUnload {
[self tearDownBuffers];
[super viewDidLoad];
}
- (void)dealloc {
[self tearDownBuffers];
[EAGLContext setCurrentContext:nil];
}
- (void)setUpBuffers {
// setup frame buffer
glGenFramebuffers(1, &_frameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
// setup color render buffer
glGenRenderbuffers(1, &_colorRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorRenderbuffer);
[self.glContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.glLayer];
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_frameBufferWidth);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_frameBufferHeight);
// check success
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
NSLog(@"Failed to make complete framebuffer object: %i", glCheckFramebufferStatus(GL_FRAMEBUFFER));
}
}
- (void)tearDownBuffers {
if (_frameBuffer) {
// delete frameBuffer
glDeleteFramebuffers(1, &_frameBuffer);
_frameBuffer = 0;
}
if (_colorRenderbuffer) {
// delete color render buffer
glDeleteRenderbuffers(1, &_colorRenderbuffer);
_colorRenderbuffer = 0;
}
}
- (void)drawFrame {
// bind framebuffer & set viewPort
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
glViewport(0, 0, _frameBufferWidth, _frameBufferHeight);
// bind shader program
[self.effect prepareToDraw];
// clear the screen
glClear(GL_COLOR_BUFFER_BIT);
glClearColor(0, 0, 0, 1.0);
// setup vertices
GLfloat vertices[] = {
-0.5f, -0.5f, -1.0f, 0.0f, 0.5f, -1.0f, 0.5f, -0.5f, -1.0f,
};
// setup colors
GLfloat colors[] = {
0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
};
//draw triangle
glEnableVertexAttribArray(GLKVertexAttribPosition);
glEnableVertexAttribArray(GLKVertexAttribColor);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FLOAT, 0, vertices);
glVertexAttribPointer(GLKVertexAttribColor, 4, GL_FLOAT, GL_FLOAT, 0, colors);
glDrawArrays(GL_TRIANGLES, 0, 3);
// present render buffer
glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer);
[self.glContext presentRenderbuffer:GL_RENDERBUFFER];
}
- 運(yùn)行結(jié)果如下
在一個(gè)真正的OpenGL
應(yīng)用中虏等,我們可能會(huì)用NSTimer
或CADisplayLink
周期性地每秒鐘調(diào)用-drawRrame
方法60次,同時(shí)會(huì)將幾何圖形生成和繪制分開以便不會(huì)每次都重新生成三角形的頂點(diǎn)(這樣也可以讓我們繪制其他的一些東西而不是一個(gè)三角形而已)适肠,不過上面這個(gè)例子已經(jīng)足夠演示了繪圖原則了博其。
十 AVPlayerLayer
最后一個(gè)圖層類型是AVPlayerLayer
。盡管它不是Core Animation
框架的一部分(AV前綴看上去像)迂猴,AVPlayerLayer是有別的框架(AVFoundation)
提供的慕淡,它和Core Animation緊密地結(jié)合在一起,提供了一個(gè)CALayer子類來顯示自定義的內(nèi)容類型沸毁。
AVPlayerLayer
是用來在iOS上播放視頻的峰髓。他是高級(jí)接口例如MPMoivePlayer
的底層實(shí)現(xiàn)傻寂,提供了顯示視頻的底層控制。AVPlayerLayer的使用相當(dāng)簡(jiǎn)單:你可以用+playerLayerWithPlayer:方法創(chuàng)建一個(gè)已經(jīng)綁定了視頻播放器的圖層携兵,或者你可以先創(chuàng)建一個(gè)圖層疾掰,然后用player屬性綁定一個(gè)AVPlayer實(shí)例。
/// 十 AVPlayerLayer
- (void)createAVPlayerLayer {
// get video url
NSURL *url = [[NSBundle mainBundle] URLForResource:@"video" withExtension:@"mp4"];
// create player and player layer
AVPlayer *player = [AVPlayer playerWithURL:url];
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
// set player layer frame and attach it to our view
playerLayer.frame = self.contentView.bounds;
[self.contentView.layer addSublayer:playerLayer];
// play the video
[player play];
- 運(yùn)行結(jié)果如下
我們用代碼創(chuàng)建了一個(gè)AVPlayerLayer
徐紧,但是我們?nèi)匀话阉砑拥搅艘粋€(gè)容器視圖中静檬,而不是直接在controller
中的主視圖上添加。這樣其實(shí)是為了可以使用自動(dòng)布局限制使得圖層在最中間并级;否則拂檩,一旦設(shè)備被旋轉(zhuǎn)了我們就要手動(dòng)重新放置位置,因?yàn)镃ore Animation并不支持自動(dòng)大小和自動(dòng)布局嘲碧。
當(dāng)然稻励,因?yàn)锳VPlayerLayer是CALayer的子類,它繼承了父類的所有特性愈涩。我們并不會(huì)受限于要在一個(gè)矩形中播放視頻望抽;下面顯示了在3D,圓角履婉,有色邊框煤篙,蒙板,陰影等效果毁腿。
// 給視頻增加變換舰蟆,邊框和圓角
- (void)createMultiAVPlayerLayer {
// get video url
NSURL *url = [[NSBundle mainBundle] URLForResource:@"video" withExtension:@"mp4"];
// create player and player layer
AVPlayer *player = [AVPlayer playerWithURL:url];
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
// set player layer frame and attach it to our view
playerLayer.frame = self.contentView.bounds;
[self.contentView.layer addSublayer:playerLayer];
// add transform layer
CATransform3D transform = CATransform3DIdentity;
transform.m34 = -1.0 / 500.0;
transform = CATransform3DRotate(transform, M_PI_4, 1, 1, 0);
playerLayer.transform = transform;
// add rounded corners and border
playerLayer.masksToBounds = YES;
playerLayer.cornerRadius = 20.0;
playerLayer.borderColor = [UIColor redColor].CGColor;
playerLayer.borderWidth = 5.0;
// play the video
[player play];
}
- 運(yùn)行效果如下
十一 總結(jié)
這一章我們簡(jiǎn)要概述了一些專用圖層以及用他們實(shí)現(xiàn)的一些效果,我們只是了解到這些圖層的皮毛狸棍,像CATiledLayer
和CAEMitterLayer
這些類可以單獨(dú)寫一章的身害。但是,重點(diǎn)是記住CALayer是用處很大的草戈,而且它并沒有為所有可能的場(chǎng)景進(jìn)行優(yōu)化塌鸯。為了獲得Core Animation最好的性能,你需要為你的工作選對(duì)正確的工具唐片,希望你能夠挖掘這些不同的CALayer子類的功能丙猬。 這一章我們通過CAEmitterLayer
和AVPlayerLayer
類簡(jiǎn)單地接觸到了一些動(dòng)畫。
本文摘自 iOS核心動(dòng)畫高級(jí)技巧 - 專用圖層
項(xiàng)目鏈接地址 - AnimationCA***Layer