圖層樹
Core Animation是一個(gè)復(fù)合引擎,它的職責(zé)是盡可能快地組合屏幕上不同的可視內(nèi)容。這些內(nèi)容被分解成獨(dú)立的圖層叹坦,存儲(chǔ)在一個(gè)叫圖層樹的體系中。在屏幕上所看見的一切內(nèi)容咱士,在底層的實(shí)現(xiàn)其實(shí)就是一個(gè)或多個(gè)圖層樹立由。
圖層與視圖
真正現(xiàn)在屏幕上顯示和做動(dòng)畫的并不是UIView,而是UIVIew所對(duì)應(yīng)的一個(gè)CALayer實(shí)例(backing layer)序厉。UIview的職責(zé)只是創(chuàng)建并管理這個(gè)layer锐膜,以確保當(dāng)子視圖在層級(jí)關(guān)系中添加或者被移除的時(shí)候,它們對(duì)應(yīng)的圖層也在圖層樹中有相應(yīng)的操作弛房。UIView僅僅是對(duì)CALayer的一個(gè)封裝道盏,它提供了對(duì)用戶交互的處理和Core Animation底層方法的高級(jí)接口。
事實(shí)上除了視圖層級(jí)和圖層樹之外文捶,還存在呈現(xiàn)樹和渲染樹荷逞,它們每一個(gè)都扮演著不同的角色。
圖層的能力
- 陰影粹排,圓角种远,帶顏色的邊框
- 3D變換
- 非矩形范圍
- 透明遮罩
- 多級(jí)非線性動(dòng)畫
寄宿圖
contents屬性
為layer添加一張寄宿圖:
layer.contents = (__bridge id)image.CGImage;
設(shè)置寄宿圖顯示模式:
layer.contentsGravity = kCAGravityResizeAspect;
設(shè)置圖片縮放比:
layer.contentsScale = [UIScreen mainScreen].scale;
設(shè)置超出部分是否顯示:
layer.masksToBounds = YES;
設(shè)置圖片顯示區(qū)域:
layer.contentsRect = CGRectMake(0, 0, 0.5, 0.5);
設(shè)置固定邊框和拉伸區(qū)域:
layer.contentsCenter = CGRectMake(0.25, 0.25, 0.5, 0.5);
自定義繪制
實(shí)現(xiàn)UIView的
-drawRect:
方法
當(dāng)視圖顯示在屏幕上的時(shí)候-drawRect:
方法就會(huì)被調(diào)用,它當(dāng)中的代碼就會(huì)利用Core Graphics繪制一個(gè)寄宿圖顽耳,然后內(nèi)容將會(huì)緩存起來直到它需要被更新(開發(fā)者調(diào)用了setNeedsDisplay
方法坠敷,或者影響視圖表象效果的屬性被改變時(shí)妙同,視圖將會(huì)被自動(dòng)重繪,如bounds
屬性)膝迎。-
實(shí)現(xiàn)CALayer的非正式協(xié)議
CALayerDelegate
當(dāng)CALayer需要被重繪時(shí)粥帚,它會(huì)通過調(diào)用下面的方法來請(qǐng)求它的代理給它一個(gè)寄宿圖來顯示。
(void)displayLayer:(CALayerCALayer *)layer;
在上面的代理方法中限次,通過設(shè)置layer的contents屬性芒涡,就可以設(shè)置一個(gè)寄宿圖。如果代理沒有實(shí)現(xiàn)
displayLayer
方法卖漫,CALayer會(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)備懊亡。
@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 blueLayer.contentsScale = [UIScreen mainScreen].scale; //add layer to our view [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
需要注意的事情:
- 當(dāng)圖層顯示在屏幕上時(shí)依啰,CALayer不會(huì)自動(dòng)重繪它的內(nèi)容,需要開發(fā)者顯示調(diào)用
-display
方法店枣。 - 當(dāng)使用CALayerDelegate繪制寄宿圖時(shí),不會(huì)對(duì)超出圖層邊界外的內(nèi)容提供繪制支持叹誉。
- 當(dāng)圖層顯示在屏幕上時(shí)依啰,CALayer不會(huì)自動(dòng)重繪它的內(nèi)容,需要開發(fā)者顯示調(diào)用
圖層幾何學(xué)
布局
與UIView的center
屬性對(duì)應(yīng)的是CALayer中的position
屬性鸯两,他們都代表了相對(duì)于父圖層anchorPoint
所在的位置。而不是視覺上所觀察到的圖層中心的位置长豁,在anchorPoint
的值不為圖層的中心時(shí)钧唐,center
和position
的值將會(huì)和視覺上圖層中心點(diǎn)的值不一致。
對(duì)于視圖和圖層來說匠襟,frame
其實(shí)是一個(gè)虛擬的屬性钝侠,是根據(jù)bounds
,position
和transform
計(jì)算而來酸舍,所以當(dāng)其中任何一個(gè)值發(fā)生改變帅韧,frame
都會(huì)變化。相反啃勉,改變frame
的值同樣會(huì)影響它們的值忽舟。
當(dāng)對(duì)圖層做變換的時(shí)候(如旋轉(zhuǎn)/縮放),frame
實(shí)際上代表了在圖層旋轉(zhuǎn)之后整個(gè)軸對(duì)其的矩形區(qū)域淮阐。此時(shí)叮阅,frame
的寬高和bounds
的寬高并不一致。
錨點(diǎn)(anchorPoint)
圖層的anchorPoint
屬性可以看做是圖層上一個(gè)不動(dòng)的點(diǎn)泣特,當(dāng)圖層進(jìn)行旋轉(zhuǎn)等操作的時(shí)候都是以此點(diǎn)為圓心進(jìn)行的浩姥,類似于用一個(gè)釘子將紙釘在平面上,無論如何旋轉(zhuǎn)状您,釘子的位置是不會(huì)動(dòng)的勒叠。在默認(rèn)情況下anchorPoint
位于圖層的中心(0.5, 0.5)兜挨。
坐標(biāo)系
CALayer給不同坐標(biāo)系之間的圖層轉(zhuǎn)換提供了一些工具類方法:
- (CGPoint)convertPoint:(CGPoint)point fromLayer:(CALayer *)layer;
- (CGPoint)convertPoint:(CGPoint)point toLayer:(CALayer *)layer;
- (CGRect)convertRect:(CGRect)rect fromLayer:(CALayer *)layer;
- (CGRect)convertRect:(CGRect)rect toLayer:(CALayer *)layer;
這些方法可以把定義在一個(gè)圖層坐標(biāo)系下的點(diǎn)或者矩形轉(zhuǎn)換成另一個(gè)圖層坐標(biāo)系下的點(diǎn)或者矩形。
垂直翻轉(zhuǎn)子圖層:
layer.geometryFlipped = YES;
Hit Testing
判斷觸摸點(diǎn)是否在圖層內(nèi):
BOOL isContain = [layer containsPoint:point];
返回圖層本身缴饭,或者包含這個(gè)坐標(biāo)點(diǎn)的葉子節(jié)點(diǎn)圖層:
CALayer *layer = [layer hitTest:point];
自動(dòng)布局
當(dāng)使用視圖的時(shí)候暑劝,可以充分利用UIView類接口暴露出來的UIViewAutoresizingMask和NSLayoutConstraintAPI,但如果想隨意控制CALayer的布局颗搂,就需要手工操作担猛。
圖層手動(dòng)布局:
- (void)layoutSublayersOfLayer:(CALayer *)layer;
當(dāng)圖層的bounds發(fā)生改變,或者圖層的-setNeedsLayout方法被調(diào)用的時(shí)候丢氢,這個(gè)函數(shù)將會(huì)被執(zhí)行傅联。這使得我們可以手動(dòng)地重新擺放或者重新調(diào)整子圖層的大小,但是不能像UIView的autoresizingMask和constraints屬性做到自適應(yīng)屏幕旋轉(zhuǎn)疚察。
視覺效果
圓角
設(shè)置圖層角曲率(默認(rèn)為0):
layer.conrnerRadius = 5.0f
圖層邊框
設(shè)置圖層邊框?qū)挾群皖伾?/p>
layer.borderWidth = 3.0f;
layer.borderColor = [UIColor greenColor].CGColor;
陰影
設(shè)置圖層陰影顏色蒸走,方向和距離,模糊度
layer.shadowColor = [UIColor orangeColor].CGColor;
layer.shadowOffset = CGSizeMake(50, 50);
layer.shadowRadius = 10;
當(dāng)圖層的masksToBounds
屬性值設(shè)置為YES
時(shí)貌嫡,所有從圖層中突出來的內(nèi)容都會(huì)被剪裁掉比驻。因此陰影效果將會(huì)失效。想要為這樣的圖層添加陰影岛抄,就需要在要添加陰影的圖層范圍上覆蓋一個(gè)只畫陰影的空?qǐng)D層别惦。
使用shadowPath
指定任意形狀的陰影:
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *layerView1;
@property (nonatomic, weak) IBOutlet UIView *layerView2;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//enable layer shadows
self.layerView1.layer.shadowOpacity = 0.5f;
self.layerView2.layer.shadowOpacity = 0.5f;
//create a square shadow
CGMutablePathRef squarePath = CGPathCreateMutable();
CGPathAddRect(squarePath, NULL, self.layerView1.bounds);
self.layerView1.layer.shadowPath = squarePath; CGPathRelease(squarePath);
//create a circular shadow
CGMutablePathRef circlePath = CGPathCreateMutable();
CGPathAddEllipseInRect(circlePath, NULL, self.layerView2.bounds);
self.layerView2.layer.shadowPath = circlePath; CGPathRelease(circlePath);
}
@end
圖層蒙版
設(shè)置圖層蒙版:
//create mask layer
CALayer *maskLayer = [CALayer layer];
maskLayer.frame = self.layerView.bounds;
UIImage *maskImage = [UIImage imageNamed:@"Cone.png"];
maskLayer.contents = (__bridge id)maskImage.CGImage;
//apply mask to image layer
self.imageView.layer.mask = maskLayer;
拉伸過濾
設(shè)置圖層拉伸過濾算法:
view.layer.magnificationFilter = kCAFilterNearest;
透明度
透明度混合疊加問題:當(dāng)現(xiàn)實(shí)一個(gè)透明度為50%的圖層時(shí),圖層的每個(gè)像素都會(huì)一半現(xiàn)實(shí)自己的顏色夫椭,另一半顯示圖層下面的顏色掸掸。當(dāng)圖層包含一個(gè)同樣50%透明的子圖層時(shí),所看到的視圖蹭秋,50%來自子視圖扰付,25%來自圖層本身的顏色,另外25%則來自背景色仁讨。這時(shí)子視圖的可見的則為75%羽莺,顯示效果將會(huì)相當(dāng)糟糕。
理想狀態(tài)下陪竿,設(shè)置一個(gè)圖層的透明度禽翼,是希望它所包含的圖層樹向一個(gè)整體一樣的透明效果。這可以通過設(shè)置Info.plist文件中的UIViewGroupOpacity為YES來達(dá)到這個(gè)效果族跛,但是這個(gè)設(shè)置會(huì)影響到這個(gè)應(yīng)用闰挡,整個(gè)app可能會(huì)受到不良影響。
另一個(gè)方法就是設(shè)置CALayer的一個(gè)叫做shouldRasterize屬性來實(shí)現(xiàn)組透明的效果礁哄,如果它被設(shè)置為YES长酗,在應(yīng)用透明度實(shí)現(xiàn)之前,圖層及其子圖層都會(huì)被整合成一個(gè)整體的圖片桐绒,這樣就沒有透明度混合的問題了夺脾。
為了啟用shouldRasterize屬性之拨,需要圖層的rasterizationScale屬性。默認(rèn)情況下咧叭,所有圖層拉伸都是1.0蚀乔, 所以如果使用了shouldRasterize屬性,就要確保你設(shè)置了rasterizationScale屬性去匹配屏幕菲茬,以防止出現(xiàn)Retina屏幕像素化的問題吉挣。
layer.shouldRasterize = YES;
layer.rasterizationScale = [UIScreen mainScreen].scale;