6.常用layer
6.1 CAShapeLayer
CAShapeLayer是一個(gè)通過矢量圖形而不是bitmap來繪制的圖層子類炉抒。你指定諸如顏色和線寬等屬性瓮孙,用CGPath來定義想要繪制的圖形亲怠,最后CAShapeLayer就自動渲染出來了贡必。當(dāng)然京革,你也可以用Core Graphics直接向原始的CALyer的內(nèi)容中繪制一個(gè)路徑疏唾,相比直下,使用CAShapeLayer有以下一些優(yōu)點(diǎn):
- 渲染快速簇宽。CAShapeLayer使用了硬件加速阴绢,繪制同一圖形會比用Core Graphics快很多工坊。
- 高效使用內(nèi)存。一個(gè)CAShapeLayer不需要像普通CALayer一樣創(chuàng)建一個(gè)寄宿圖形恋拷,所以無論有多大,都不會占用太多的內(nèi)存厅缺。
- 不會被圖層邊界剪裁掉蔬顾。一個(gè)CAShapeLayer可以在邊界之外繪制。你的圖層路徑不會像在使用Core - Graphics的普通CALayer一樣被剪裁掉
- 不會出現(xiàn)像素化湘捎。當(dāng)你給CAShapeLayer做3D變換時(shí)诀豁,它不像一個(gè)有寄宿圖的普通圖層一樣變得像素化。
畫一個(gè)火柴人
#import "DrawingView.h"
#import
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *containerView;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//create path
UIBezierPath *path = [[UIBezierPath alloc] init];
[path moveToPoint:CGPointMake(175, 100)];
[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.containerView.layer addSublayer:shapeLayer];
}
@end
圓角
我們創(chuàng)建圓角矩形其實(shí)就是人工繪制單獨(dú)的直線和弧度窥妇,但是事實(shí)上UIBezierPath有自動繪制圓角矩形的構(gòu)造方法舷胜,下面這段代碼繪制了一個(gè)有三個(gè)圓角一個(gè)直角的矩形:
//define path parameters
CGRect rect = CGRectMake(50, 50, 100, 100);
CGSize radii = CGSizeMake(20, 20);
UIRectCorner corners = UIRectCornerTopRight | UIRectCornerBottomRight | UIRectCornerBottomLeft;
//create path
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:corners cornerRadii:radii];
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = path.CGPath;
// 蒙板
self.contentView.layer.mask = shapeLayer;
我們可以通過這個(gè)圖層路徑繪制一個(gè)既有直角又有圓角的視圖。如果我們想依照此圖形來剪裁視圖內(nèi)容活翩,我們可以把CAShapeLayer作為視圖的宿主圖層烹骨,而不是添加一個(gè)子視圖
6.2 CATextLayer
如果你想在一個(gè)圖層里面顯示文字,完全可以借助圖層代理直接將字符串使用Core Graphics寫入圖層的內(nèi)容(這就是UILabel的精髓)材泄。
CATextLayer也要比UILabel渲染得快得多沮焕。很少有人知道在iOS 6及之前的版本,UILabel其實(shí)是通過WebKit來實(shí)現(xiàn)繪制的拉宗,這樣就造成了當(dāng)有很多文字的時(shí)候就會有極大的性能壓力峦树。而CATextLayer使用了Core text,并且渲染得非车┦拢快魁巩。
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *labelView;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//create a text layer
CATextLayer *textLayer = [CATextLayer layer];
textLayer.frame = self.labelView.bounds;
[self.labelView.layer addSublayer:textLayer];
//set text attributes
textLayer.foregroundColor = [UIColor blackColor].CGColor;
textLayer.alignmentMode = kCAAlignmentJustified;
textLayer.wrapped = YES;
//choose a font
UIFont *font = [UIFont systemFontOfSize:15];
//set layer font
CFStringRef fontName = (__bridge CFStringRef)font.fontName;
CGFontRef fontRef = CGFontCreateWithFontName(fontName);
textLayer.font = fontRef;
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;
}
@end
如果你仔細(xì)看這個(gè)文本,你會發(fā)現(xiàn)一個(gè)奇怪的地方:這些文本有一些像素化了姐浮。這是因?yàn)椴]有以Retina的方式渲染谷遂,contentScale屬性,用來決定圖層內(nèi)容應(yīng)該以怎樣的分辨率來渲染卖鲤。contentsScale并不關(guān)心屏幕的拉伸因素而總是默認(rèn)為1.0埋凯。如果我們想以Retina的質(zhì)量來顯示文字点楼,我們就得手動地設(shè)置CATextLayer的contentsScale屬性,如下:
textLayer.contentsScale = [UIScreen mainScreen].scale;
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è)例子會告訴你如何將UIFont轉(zhuǎn)換成CGFontRef。
另外条摸,CATextLayer的string屬性并不是你想象的NSString類型悦污,而是id類型。這樣你既可以用NSString也可以用NSAttributedString來指定文本了(注意钉蒲,NSAttributedString并不是NSString的子類)切端。屬性化字符串是iOS用來渲染字體風(fēng)格的機(jī)制,它以特定的方式來決定指定范圍內(nèi)的字符串的原始信息顷啼,比如字體踏枣,顏色,字重钙蒙,斜體等茵瀑。
富文本
iOS 6中,Apple給UILabel和其他UIKit文本視圖添加了直接的屬性化字符串的支持躬厌,應(yīng)該說這是一個(gè)很方便的特性马昨。不過事實(shí)上從iOS3.2開始CATextLayer就已經(jīng)支持屬性化字符串了。這樣的話扛施,如果你想要支持更低版本的iOS系統(tǒng)鸿捧,CATextLayer無疑是你向界面中增加富文本的好辦法,而且也不用去跟復(fù)雜的Core Text打交道疙渣,也省了用UIWebView的麻煩笛谦。
#import "DrawingView.h"
#import
#import
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *labelView;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//create a text layer
CATextLayer *textLayer = [CATextLayer layer];
textLayer.frame = self.labelView.bounds;
textLayer.contentsScale = [UIScreen mainScreen].scale;
[self.labelView.layer addSublayer:textLayer];
//set text attributes
textLayer.alignmentMode = kCAAlignmentJustified;
textLayer.wrapped = YES;
//choose a font
UIFont *font = [UIFont systemFontOfSize:15];
//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";
//create attributed string
NSMutableAttributedString *string = nil;
string = [[NSMutableAttributedString alloc] initWithString:text];
//convert UIFont to a CTFont
CFStringRef fontName = (__bridge CFStringRef)font.fontName;
CGFloat fontSize = font.pointSize;
CTFontRef fontRef = CTFontCreateWithName(fontName, fontSize, NULL);
//set text attributes
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;
}
@end
行距和字距
有必要提一下的是,由于繪制的實(shí)現(xiàn)機(jī)制不同(Core Text和WebKit)昌阿,用CATextLayer渲染和用UILabel渲染出的文本行距和字距也不是不盡相同的饥脑。
二者的差異程度(由使用的字體和字符決定)總的來說挺小,但是如果你想正確的顯示普通便簽和CATextLayer就一定要記住這一點(diǎn)懦冰。
UILabel的替代品
我們已經(jīng)證實(shí)了CATextLayer比UILabel有著更好的性能表現(xiàn)灶轰,同時(shí)還有額外的布局選項(xiàng)并且在iOS 5上支持富文本。但是與一般的標(biāo)簽比較而言會更加繁瑣一些刷钢。如果我們真的在需求一個(gè)UILabel的可用替代品笋颤,最好是能夠在Interface Builder上創(chuàng)建我們的標(biāo)簽,而且盡可能地像一般的視圖一樣正常工作。
我們應(yīng)該繼承UILabel伴澄,然后添加一個(gè)子圖層CATextLayer并重寫顯示文本的方法赋除。但是仍然會有由UILabel
的-drawRect:方法創(chuàng)建的空寄宿圖。而且由于CALayer不支持自動縮放和自動布局非凌,子視圖并不是主動跟蹤視圖邊界的大小举农,所以每次視圖大小被更改,我們不得不手動更新子圖層的邊界敞嗡。
我們真正想要的是一個(gè)用CATextLayer作為宿主圖層的UILabel子類颁糟,這樣就可以隨著視圖自動調(diào)整大小而且也沒有冗余的寄宿圖啦。
就像我們在第一章『圖層樹』討論的一樣喉悴,每一個(gè)UIView都是寄宿在一個(gè)CALayer的示例上棱貌。這個(gè)圖層是由視圖自動創(chuàng)建和管理的,那我們可以用別的圖層類型替代它么箕肃?一旦被創(chuàng)建婚脱,我們就無法代替這個(gè)圖層了。但是如果我們繼承了UIView勺像,那我們就可以重寫+layerClass方法使得在創(chuàng)建的時(shí)候能返回一個(gè)不同的圖層子類障贸。UIView
會在初始化的時(shí)候調(diào)用+layerClass方法,然后用它的返回類型來創(chuàng)建宿主圖層咏删。
#import "LayerLabel.h"
#import
@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];
}
- (id)initWithFrame:(CGRect)frame
{
//called when creating label programmatically
if (self = [super initWithFrame:frame]) {
[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)setTextColor:(UIColor *)textColor
{
super.textColor = textColor;
//set layer text color
[self textLayer].foregroundColor = textColor.CGColor;
}
- (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)行代碼惹想,你會發(fā)現(xiàn)文本并沒有像素化问词,而我們也沒有設(shè)置contentsScale屬性督函。把CATextLayer作為宿主圖層的另一好處就是視圖自動設(shè)置了contentsScale屬性。
在這個(gè)簡單的例子中激挪,我們只是實(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è)簡單可復(fù)用的方法叫倍。
6.3 CATransformLayer
如果你用一個(gè)普通的layer去承載多個(gè)子layer。是無法展現(xiàn)出豺瘤,3d效果的吆倦。用CATransformLayer就可以。
CATransformLayer不同于普通的CALayer坐求,因?yàn)樗荒茱@示它自己的內(nèi)容蚕泽。只有當(dāng)存在了一個(gè)能作用域子圖層的變換它才真正存在。CATransformLayer并不平面化它的子圖層桥嗤,所以它能夠用于構(gòu)造一個(gè)層級的3D結(jié)構(gòu)
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *containerView;
@end
@implementation ViewController
- (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.containerView.bounds.size;
cube.position = CGPointMake(containerSize.width / 2.0, containerSize.height / 2.0);
//apply the transform and return
cube.transform = transform;
return cube;
}
- (void)viewDidLoad
{
[super viewDidLoad];
//set up the perspective transform
CATransform3D pt = CATransform3DIdentity;
pt.m34 = -1.0 / 500.0;
self.containerView.layer.sublayerTransform = pt;
//set up the transform for cube 1 and add it
CATransform3D c1t = CATransform3DIdentity;
c1t = CATransform3DTranslate(c1t, -100, 0, 0);
CALayer *cube1 = [self cubeWithTransform:c1t];
[self.containerView.layer addSublayer:cube1];
//set up the transform for cube 2 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.containerView.layer addSublayer:cube2];
}
@end
6.4 CAGradientLayer
CAGradientLayer是用來生成兩種或更多顏色平滑漸變的须妻。用Core Graphics復(fù)制一個(gè)CAGradientLayer并將內(nèi)容繪制到一個(gè)普通圖層的寄宿圖也是有可能的仔蝌,但是CAGradientLayer的真正好處在于繪制使用了硬件加速。
基礎(chǔ)漸變
我們將從一個(gè)簡單的紅變藍(lán)的對角線漸變開始.這些漸變色彩放在一個(gè)數(shù)組中荒吏,并賦給colors屬性敛惊。這個(gè)數(shù)組成員接受CGColorRef類型的值(并不是從NSObject派生而來),所以我們要用通過bridge轉(zhuǎn)換以確保編譯正常司倚。
CAGradientLayer也有startPoint和endPoint屬性豆混,他們決定了漸變的方向。這兩個(gè)參數(shù)是以單位坐標(biāo)系進(jìn)行的定義动知,所以左上角坐標(biāo)是{0, 0}皿伺,右下角坐標(biāo)是{1, 1}。
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *containerView;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//create gradient layer and add it to our container view
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = self.containerView.bounds;
[self.containerView.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);
}
@end
多重漸變
如果你愿意盒粮,colors屬性可以包含很多顏色鸵鸥,所以創(chuàng)建一個(gè)彩虹一樣的多重漸變也是很簡單的。默認(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ù)組大小一定要相同瘦赫,否則你將會得到一個(gè)空白的漸變。
- (void)viewDidLoad {
[super viewDidLoad];
//create gradient layer and add it to our container view
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = self.containerView.bounds;
[self.containerView.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);
}
6.5 CAReplicatorLayer
CAReplicatorLayer的目的是為了高效生成許多相似的圖層蛤迎。它會繪制一個(gè)或多個(gè)圖層的子圖層确虱,并在每個(gè)復(fù)制體上應(yīng)用不同的變換√骜桑看上去演示能夠更加解釋這些校辩,我們來寫個(gè)例子吧。
重復(fù)圖層(Repeating Layers)
我們在屏幕的中間創(chuàng)建了一個(gè)小白色方塊圖層辆童,然后用CAReplicatorLayer生成十個(gè)圖層組成一個(gè)圓圈宜咒。instanceCount屬性指定了圖層需要重復(fù)多少次。instanceTransform指定了一個(gè)CATransform3D3D變換(這種情況下胸遇,下一圖層的位移和旋轉(zhuǎn)將會移動到圓圈的下一個(gè)點(diǎn))荧呐。
變換是逐步增加的,每個(gè)實(shí)例都是相對于前一實(shí)例布局。這就是為什么這些復(fù)制體最終不會出現(xiàn)在同意位置上倍阐,
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *containerView;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//create a replicator layer and add it to our view
CAReplicatorLayer *replicator = [CAReplicatorLayer layer];
replicator.frame = self.containerView.bounds;
[self.containerView.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];
}
@end
注意到當(dāng)圖層在重復(fù)的時(shí)候概疆,他們的顏色也在變化:這是用instanceBlueOffset和instanceGreenOffset屬性實(shí)現(xiàn)的。通過逐步減少藍(lán)色和綠色通道峰搪,我們逐漸將圖層顏色轉(zhuǎn)換成了紅色岔冀。這個(gè)復(fù)制效果看起來很酷,但是CAReplicatorLayer真正應(yīng)用到實(shí)際程序上的場景比如:一個(gè)游戲中導(dǎo)彈的軌跡云概耻,或者粒子爆炸(盡管iOS 5已經(jīng)引入了CAEmitterLayer使套,它更適合創(chuàng)建任意的粒子效果)。除此之外鞠柄,還有一個(gè)實(shí)際應(yīng)用是:反射侦高。
反射
使用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,它會自動產(chǎn)生內(nèi)容的反射效果夯尽。
#import "ReflectionView.h"
#import
@implementation ReflectionView
+ (Class)layerClass
{
return [CAReplicatorLayer class];
}
- (void)setUp
{
//configure replicator
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 + 2;
transform = CATransform3DTranslate(transform, 0, verticalOffset, 0);
transform = CATransform3DScale(transform, 1, -1, 0);
layer.instanceTransform = transform;
//reduce alpha of reflection layer
layer.instanceAlphaOffset = -0.6;
}
- (id)initWithFrame:(CGRect)frame
{
//this is called when view is created in code
if ((self = [super initWithFrame:frame])) {
[self setUp];
}
return self;
}
- (void)awakeFromNib
{
//this is called when view is created from a nib
[self setUp];
}
@end
實(shí)際上用ReflectionView實(shí)現(xiàn)這個(gè)效果會更簡單瞧壮,我們只需要把ReflectionView的實(shí)例放置于Interface Builder,它就會實(shí)時(shí)生成子視圖的反射匙握,而不需要別的代碼
6.6 CAScrollLayer
6.7 CATiledLayer
6.8 CAEmitterLayer
在iOS 5中咆槽,蘋果引入了一個(gè)新的CALayer子類叫做CAEmitterLayer。CAEmitterLayer是一個(gè)高性能的粒子引擎圈纺,被用來創(chuàng)建實(shí)時(shí)例子動畫如:煙霧秦忿,火,雨等等這些效果赠堵。
CAEmitterLayer看上去像是許多CAEmitterCell的容器小渊,這些CAEmitierCell定義了一個(gè)例子效果法褥。你將會為不同的例子效果定義一個(gè)或多個(gè)CAEmitterCell作為模版茫叭,同時(shí)CAEmitterLayer負(fù)責(zé)基于這些模版實(shí)例化一個(gè)粒子流。一個(gè)CAEmitterCell類似于一個(gè)CALayer:它有一個(gè)contents屬性可以定義為一個(gè)CGImage半等,另外還有一些可設(shè)置屬性控制著表現(xiàn)和行為揍愁。
#import "ViewController.h"
#import
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *containerView;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//create particle emitter layer
CAEmitterLayer *emitter = [CAEmitterLayer layer];
emitter.frame = self.containerView.bounds;
[self.containerView.layer addSublayer:emitter];
//configure emitter
emitter.renderMode = kCAEmitterLayerAdditive;
emitter.emitterPosition = CGPointMake(emitter.frame.size.width / 2.0, emitter.frame.size.height / 2.0);
//create a particle template
CAEmitterCell *cell = [[CAEmitterCell alloc] init];
cell.contents = (__bridge id)[UIImage imageNamed:@"Spark.png"].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];
}
@end
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中也有扬蕊。這些屬性會以相乘的方式作用在一起,這樣你就可以用一個(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毁渗,效果就沒那么好看了
6.9 CAEAGLLayer
當(dāng)iOS要處理高性能圖形繪制践磅,必要時(shí)就是OpenGL。應(yīng)該說它應(yīng)該是最后的殺手锏灸异,至少對于非游戲的應(yīng)用來說是的府适。因?yàn)橄啾菴ore Animation和UIkit框架,它不可思議地復(fù)雜肺樟。
你需要將GLKit和OpenGLES框架加入到你的項(xiàng)目中檐春,然后就可以實(shí)現(xiàn)清單6.14中的代碼,里面是設(shè)置一個(gè)GAEAGLLayer的最少工作么伯,它使用了OpenGL ES 2.0 的繪圖上下文疟暖,并渲染了一個(gè)有色三角
#import "ViewController.h"
#import
#import
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *glView;
@property (nonatomic, strong) EAGLContext *glContext;
@property (nonatomic, strong) CAEAGLLayer *glLayer;
@property (nonatomic, assign) GLuint framebuffer;
@property (nonatomic, assign) GLuint colorRenderbuffer;
@property (nonatomic, assign) GLint framebufferWidth;
@property (nonatomic, assign) GLint framebufferHeight;
@property (nonatomic, strong) GLKBaseEffect *effect;
@end
@implementation ViewController
- (void)setUpBuffers
{
//set up frame buffer
glGenFramebuffers(1, &_framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
//set up 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.0, 0.0, 1.0);
//set up vertices
GLfloat vertices[] = {
-0.5f, -0.5f, -1.0f, 0.0f, 0.5f, -1.0f, 0.5f, -0.5f, -1.0f,
};
//set up 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_FALSE, 0, vertices);
glVertexAttribPointer(GLKVertexAttribColor,4, GL_FLOAT, GL_FALSE, 0, colors);
glDrawArrays(GL_TRIANGLES, 0, 3);
//present render buffer
glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer);
[self.glContext presentRenderbuffer:GL_RENDERBUFFER];
}
- (void)viewDidLoad
{
[super viewDidLoad];
//set up context
self.glContext = [[EAGLContext alloc] initWithAPI: kEAGLRenderingAPIOpenGLES2];
[EAGLContext setCurrentContext:self.glContext];
//set up layer
self.glLayer = [CAEAGLLayer layer];
self.glLayer.frame = self.glView.bounds;
[self.glView.layer addSublayer:self.glLayer];
self.glLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking:@NO, kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8};
//set up base effect
self.effect = [[GLKBaseEffect alloc] init];
//set up buffers
[self setUpBuffers];
//draw frame
[self drawFrame];
}
- (void)viewDidUnload
{
[self tearDownBuffers];
[super viewDidUnload];
}
- (void)dealloc
{
[self tearDownBuffers];
[EAGLContext setCurrentContext:nil];
}
@end
在一個(gè)真正的OpenGL應(yīng)用中,我們可能會用NSTimer或CADisplayLink周期性地每秒鐘調(diào)用-drawRrame方法60次田柔,同時(shí)會將幾何圖形生成和繪制分開以便不會每次都重新生成三角形的頂點(diǎn)(這樣也可以讓我們繪制其他的一些東西而不是一個(gè)三角形而已)俐巴,不過上面這個(gè)例子已經(jīng)足夠演示了繪圖原則了。
6.10 AVPlayerLayer
盡管它不是Core Animation框架的一部分(AV前綴看上去像)硬爆,AVPlayerLayer是有別的框架(AVFoundation)提供的欣舵,它和Core Animation緊密地結(jié)合在一起,提供了一個(gè)CALayer子類來顯示自定義的內(nèi)容類型缀磕。
AVPlayerLayer是用來在iOS上播放視頻的缘圈。他是高級接口例如MPMoivePlayer的底層實(shí)現(xiàn)劣光,提供了顯示視頻的底層控制。AVPlayerLayer的使用相當(dāng)簡單:你可以用+playerLayerWithPlayer:
方法創(chuàng)建一個(gè)已經(jīng)綁定了視頻播放器的圖層糟把,或者你可以先創(chuàng)建一個(gè)圖層赎线,然后用player屬性綁定一個(gè)AVPlayer實(shí)例。
在我們開始之前糊饱,我們需要添加AVFoundation到我們的項(xiàng)目中垂寥。
#import "ViewController.h"
#import
#import
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *containerView; @end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//get video URL
NSURL *URL = [[NSBundle mainBundle] URLForResource:@"Ship" 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.containerView.bounds;
[self.containerView.layer addSublayer:playerLayer];
//play the video
[player play];
}
@end
當(dāng)然,因?yàn)锳VPlayerLayer是CALayer的子類另锋,它繼承了父類的所有特性滞项。我們并不會受限于要在一個(gè)矩形中播放視頻;清單6.16演示了在3D夭坪,圓角文判,有色邊框,蒙板室梅,陰影等效果
- (void)viewDidLoad
{
...
//set player layer frame and attach it to our view
playerLayer.frame = self.containerView.bounds;
[self.containerView.layer addSublayer:playerLayer];
//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];
}