[iOS] 核心高級(jí)動(dòng)畫(huà)技巧 — Part1

Core Animation不是只和動(dòng)畫(huà)相關(guān)哦楼熄,它的原名是Layer Kit拗盒,它的職責(zé)就是盡可能快地組合屏幕上不同的可視內(nèi)容础钠,這個(gè)內(nèi)容是被分解成獨(dú)立的圖層恰力,存儲(chǔ)在一個(gè)叫做圖層樹(shù)的體系之中。于是這個(gè)樹(shù)形成了UIKit以及在iOS應(yīng)用程序當(dāng)中你所能在屏幕上看見(jiàn)的一切的基礎(chǔ)旗吁。

為什么iOS要基于UIView和CALayer提供兩個(gè)平行的層級(jí)關(guān)系呢踩萎?為什么不用一個(gè)簡(jiǎn)單的層級(jí)來(lái)處理所有事情呢?

原因在于要做職責(zé)分離很钓,這樣也能避免很多重復(fù)代碼香府。例如在iOS和Mac OS兩個(gè)平臺(tái)上,事件和用戶(hù)交互有很多地方的不同码倦,基于多點(diǎn)觸控的用戶(hù)界面和基于鼠標(biāo)鍵盤(pán)有著本質(zhì)的區(qū)別企孩,但是繪制的部分是一致的可以共享。

CALayer的寄宿圖

在設(shè)置圖層圖片時(shí)叹洲,需要給contents賦值柠硕。其定義如下:

@property(nullable, strong) id contents; 

可以看到,它是一個(gè)id類(lèi)型的對(duì)象,但是這是為了兼容OSX開(kāi)發(fā)中CoreAnimation蝗柔、AppKit中的CGImage和NSImage兩種類(lèi)型而設(shè)置闻葵,對(duì)于UIKit的UIImage并不支持。

UIImage: 存在CGImage屬性(Cocoa下對(duì)象)癣丧,返回“CGImageRef”槽畔,返回的是CoreFundation下的類(lèi)型,因此需要類(lèi)型轉(zhuǎn)化一下:

layer.contents = (__bridge id)image.CGImage;

由于轉(zhuǎn)換的是CGImageRef胁编,我們知道C類(lèi)型是不受ARC管理的厢钧,所以需要進(jìn)行類(lèi)型橋接。

如果你設(shè)置給content的類(lèi)型不是image嬉橙,那么這個(gè)圖層就啥也不顯示早直,其實(shí)就是沒(méi)有成功被轉(zhuǎn)換。


UIView.contentMode = CALayer.contentGravity

設(shè)置view的contetMode其實(shí)就是對(duì)layer設(shè)置:

@property(copy) NSString *contentsGravity; 

它是NSString類(lèi)型市框,可選的常量值為kCAGravity**霞扬。
對(duì)view設(shè)置contetMode為AspectFit等同self.layerView.layer.contentsGravity = kCAGravityResizeAspect;


contentScale

屬性定義了寄宿圖的像素尺寸和視圖大小的比例,默認(rèn)情況下它是一個(gè)值為1.0的浮點(diǎn)數(shù)枫振。它用來(lái)判斷在繪制圖層的時(shí)候應(yīng)該為寄宿圖創(chuàng)建的空間大小喻圃,和需要顯示的圖片的拉伸度。

如果設(shè)置為1.0粪滤,將會(huì)以每個(gè)點(diǎn)1個(gè)像素繪制圖片斧拍,如果設(shè)置為 2.0,則會(huì)以每個(gè)點(diǎn)2個(gè)像素繪制圖片杖小,其實(shí)也就是我們熟悉的2x 3x的意思肆汹。

當(dāng)設(shè)置layer的contentGravity為kCAGravityCenter(這個(gè)值不會(huì)拉伸圖片),并且通過(guò)設(shè)置圖片的方式設(shè)置layer的content予权,那么這時(shí)你會(huì)發(fā)現(xiàn)圖片大了一倍(如果是3x就是三倍)县踢。因?yàn)闀?huì)自動(dòng)讀取高分辨率的圖片,卻不會(huì)自動(dòng)設(shè)置contentScale為正確的數(shù)值伟件,仍舊默認(rèn)設(shè)置為1硼啤,就會(huì)造成過(guò)大的情況。(類(lèi)型轉(zhuǎn)換過(guò)程中丟失了縮放因子)

舉個(gè)栗子斧账,如果圖片大小本來(lái)是3030個(gè)點(diǎn)谴返,在3x的設(shè)備上會(huì)讀取9090像素的圖片,然后設(shè)置顯示的時(shí)候卻默認(rèn)告訴設(shè)備按照一個(gè)點(diǎn)一個(gè)像素的方式顯示咧织,于是這個(gè)圖片的大小就變?yōu)榱?0*90個(gè)點(diǎn)嗓袱,也就大了3倍。

所以如果是這種情形需要手動(dòng)設(shè)置:

layer.contentsScale = [UIScreen mainScreen].scale;

maskToBounds

對(duì)應(yīng)UIView的clipsToBounds屬性习绢,即是否繪制超過(guò)了邊界的部分渠抹。


contentRect

CALayer的contentsRect屬性允許我們?cè)趫D層邊框里顯示寄宿圖的一個(gè)子域蝙昙,它使用了單位坐標(biāo),單位坐標(biāo)指定在0到1之間梧却,是一個(gè)相對(duì)值(像素和點(diǎn)就是絕對(duì)值)奇颠。

所以他們是相對(duì)與寄宿圖的尺寸的。默認(rèn)的contentsRect是{0, 0, 1, 1}放航,這意味著整個(gè)寄宿圖默認(rèn)都是可見(jiàn)的烈拒,如果我們指定一個(gè)小一點(diǎn)的矩形,圖片就會(huì)被裁剪广鳍。

contentRect

這個(gè)的一個(gè)比較好的應(yīng)用是類(lèi)似Cocos Creator里面的圖集荆几,就是把多張圖片放到一張上面,然后通過(guò)contentRect截取你要的部分赊时,這樣可以減少載入時(shí)間(單張大圖載入時(shí)間小于多張小圖)吨铸、內(nèi)存使用、渲染消耗祖秒。

圖集

但是如果你要改其中一張的尺寸其他都得改比較麻煩焊傅。。有些軟件可以自己拼合圖片并且輸出xml文件記錄各個(gè)圖片的位置狈涮,這樣就可以不用代碼寫(xiě)死啦,讀xml并設(shè)置就好啦鸭栖。


contentsCenter

contentsCenter其實(shí)是一個(gè)CGRect歌馍,它定義了一個(gè)固定的邊框和一個(gè)在圖層上可拉伸的區(qū)域。 有點(diǎn)類(lèi)似點(diǎn)九圖的感覺(jué)晕鹊。

默認(rèn)情況下松却,contentsCenter是{0, 0, 1, 1},這意味著如果大薪啊(由contentsGravity決定)改變了,那么寄宿圖將會(huì)均勻地拉伸開(kāi)晓锻。但是如果我們?cè)黾釉c(diǎn)的值并減小尺寸。我們會(huì)在圖片的周?chē)鷦?chuàng)造一個(gè)邊框飞几。下圖展示了contentsCenter設(shè)置為{0.25, 0.25, 0.5, 0.5}的效果砚哆。

可拉伸區(qū)域contentCenter

其實(shí)在xib里面也可以設(shè)置哦,就是Stretching~


Custom Drawing自定義繪制

給Contents賦CGImage的值不是唯一的設(shè)置寄宿圖的方法屑墨,我們也可以直接用Core Graphics直接繪制寄宿圖躁锁。能夠通過(guò)繼承UIView并實(shí)現(xiàn)-drawRect: 方法來(lái)自定義繪制。

drawRect: 方法沒(méi)有默認(rèn)的實(shí)現(xiàn)卵史,因?yàn)閷?duì)UIView來(lái)說(shuō)战转,寄宿圖并不是必須的。如果UIView檢測(cè)到-drawRect:方法被調(diào)用了以躯,它就會(huì)為視圖分配一個(gè)寄宿圖槐秧,這個(gè)寄宿圖的像素尺寸等于視圖大小乘以contentsScale的值。如果你不需要寄宿圖,那就不要?jiǎng)?chuàng)建這個(gè)方法了刁标,這會(huì)造成CPU資源和內(nèi)存的浪費(fèi)颠通,這就是為什么蘋(píng)果建議:如果沒(méi)有自定義繪制的任務(wù)就不要在子類(lèi)中寫(xiě)一個(gè)空-drawRect:方法

當(dāng)setNeedsDisplay被調(diào)用的時(shí)候命雀,上次drawRect繪制結(jié)果產(chǎn)生的緩存才會(huì)被清空蒜哀,進(jìn)行重繪,表現(xiàn)上雖然是當(dāng)你更改一些UIView的屬性的時(shí)候進(jìn)行了重繪吏砂,其實(shí)底層都是setNeedsDisplay撵儿。

而再底層一點(diǎn),其實(shí)UIView的重繪是依賴(lài)于CALayer來(lái)重繪并且緩存產(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ō)沒(méi)有CALayerDelegate @protocol可以讓你在類(lèi)里面引用啦缀匕。(一般layer的delegate都是view纳决,盡量別改)

當(dāng)需要重繪時(shí),CALayer會(huì)請(qǐng)求它的代理給他一個(gè)寄宿圖來(lái)顯示乡小,通過(guò)調(diào)用以下方法:

- (void)displayLayer:(CALayer *)layer;

如果代理不實(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è)合適尺寸的空寄宿圖和一個(gè)Core Graphics的繪制上下文環(huán)境满钟,為繪制寄宿圖做準(zhǔn)備胜榔,他作為ctx參數(shù)傳入。

@interface LayerViewController () <CALayerDelegate>{
    UIImageView *testView;
}

@end

@implementation LayerViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    testView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 200, 200)];
    [self.view addSubview:testView];
    
    CALayer *yellowLayer = [[CALayer alloc] init];
    yellowLayer.frame = CGRectMake(50, 50, 100, 100);
    yellowLayer.backgroundColor = [UIColor yellowColor].CGColor;
    yellowLayer.delegate = self;
    [testView.layer addSublayer:yellowLayer];
    
    // [yellowLayer display];
    
    testView.layer.cornerRadius = 20;
    
    testView.image = [UIImage imageNamed:@"li.jpg"];
}

- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
    CGContextSetLineWidth(ctx, 10);
    CGContextSetStrokeColorWithColor(ctx, [UIColor blackColor].CGColor);
    CGContextStrokeEllipseInRect(ctx, layer.bounds);
}

@end

上面的代碼如果運(yùn)行會(huì)發(fā)現(xiàn)只有一張圖片上面有一塊黃色湃番,但并沒(méi)有橢圓夭织,這是因?yàn)闆](méi)有執(zhí)行[yellowLayer display],如果解開(kāi)注釋就會(huì)有啦吠撮,但是也不會(huì)超出范圍繪制哦尊惰。

  • 不同于UIView,當(dāng)圖層顯示在屏幕上時(shí)泥兰,CALayer不會(huì)自動(dòng)重繪它的內(nèi)容(調(diào)用delegate的繪制)择浊。它把重繪的決定權(quán)交給了開(kāi)發(fā)者,需要手動(dòng)在layer上調(diào)用-display逾条。

  • 盡管我們沒(méi)使用masksToBounds屬性琢岩,繪制的那個(gè)圓仍然沿邊界被裁剪了。這是因?yàn)楫?dāng)你使用CALayerDelegate繪制寄宿圖的時(shí)候师脂,并沒(méi)有對(duì)超出邊界外的內(nèi)容提供繪制担孔。
    這個(gè)我理解就是傳入的ctx是已經(jīng)計(jì)算好尺寸的畫(huà)布了江锨, 就不能繪制超出畫(huà)布的區(qū)域。

當(dāng)創(chuàng)建UIView時(shí)糕篇,它會(huì)自動(dòng)把圖層的delegate設(shè)置為自己啄育,如果需要繪制就重寫(xiě)drawRect,如果需要重繪UIView會(huì)自己幫我們調(diào)用layer的display拌消。


布局

  • UIView有三個(gè)比較重要的布局屬性:frame挑豌,bounds和center
  • CALayer對(duì)應(yīng)的叫:frame,bounds和position

frame代表了圖層的外部坐標(biāo)(也就是在父圖層上占據(jù)的空間)
bounds是內(nèi)部坐標(biāo)({0墩崩, 0}通常是指圖層的左上角)
center和position都代表了相對(duì)于父圖層anchorPoint所在的位置氓英。

布局屬性

視圖的frame,bounds和center屬性?xún)H僅是存取方法鹦筹,當(dāng)操作視圖的frame铝阐,實(shí)際上是在改變位于視圖下方CALayer的frame,不能夠獨(dú)立于layer之外改變視圖的frame铐拐。

當(dāng)我們修改一view的frame的時(shí)候徘键,其實(shí)改變的是layer的,就和改view的背景顏色其實(shí)實(shí)際改的是layer的顏色遍蟋。

bounds是左上角相對(duì)于自己坐標(biāo)系的坐標(biāo)吹害,所以如果將bounds改為(-10,-10)虚青,那么相當(dāng)于將坐標(biāo)系向右下移動(dòng)了10它呀,那么所有子view都會(huì)像右下移動(dòng)10。這里注意父view不會(huì)動(dòng)挟憔,是子view移動(dòng)哦

更改bounds的大小,bounds的大小代表當(dāng)前視圖的長(zhǎng)和寬烟号,修改長(zhǎng)寬后绊谭,中心點(diǎn)繼續(xù)保持不變, 長(zhǎng)寬進(jìn)行改變;通過(guò)bounds修改長(zhǎng)寬看起來(lái)就像是以中心點(diǎn)為基準(zhǔn)點(diǎn)對(duì)長(zhǎng)寬兩邊同時(shí)進(jìn)行縮放汪拥。

frame其實(shí)是虛擬屬性达传,是由bounds、position和transform計(jì)算得到的迫筑,當(dāng)任一因素改變的時(shí)候宪赶,frame就會(huì)變;如果frame改變脯燃,同理position也會(huì)受到影響改變搂妻。


當(dāng)對(duì)圖層做變換的時(shí)候,比如旋轉(zhuǎn)或者縮放辕棚,frame實(shí)際上代表了覆蓋在圖層旋轉(zhuǎn)之后的整個(gè)軸對(duì)齊的矩形區(qū)域欲主,也就是說(shuō)frame的寬高可能和bounds不在一致了邓厕。

圖片旋轉(zhuǎn)

anchorPoint

圖層的anchorPoint通過(guò)position來(lái)控制它的frame的位置,改變anchorPoint并不會(huì)改變position扁瓢,所以anchorPoint可以被理解為用于移動(dòng)視圖的把柄详恼。

默認(rèn)來(lái)說(shuō),anchorPoint位于圖層的中間點(diǎn)引几,所以圖層的將會(huì)以這個(gè)點(diǎn)為中心放置昧互。anchorPoint并沒(méi)有被UIView暴露出來(lái),這也是視圖的position屬性被叫做center的原因伟桅。但是圖層的anchorPoint可以被移動(dòng)敞掘。

移動(dòng)anchorPoint

坐標(biāo)系轉(zhuǎn)換

view提供一些方法可以將其他view坐標(biāo)系中的坐標(biāo)點(diǎn)轉(zhuǎn)換到當(dāng)前view的坐標(biāo)系中,或者將自己坐標(biāo)系的點(diǎn)轉(zhuǎn)換到其他view的坐標(biāo)系中:

// 將像素point由point所在視圖轉(zhuǎn)換到目標(biāo)視圖view中贿讹,返回在目標(biāo)視圖view中的像素值
- (CGPoint)convertPoint:(CGPoint)point toView:(UIView *)view;

// 將像素point從view中轉(zhuǎn)換到當(dāng)前視圖中渐逃,返回在當(dāng)前視圖中的位置
- (CGPoint)convertPoint:(CGPoint)point fromView:(UIView *)view;

// 將rect由rect所在視圖轉(zhuǎn)換到目標(biāo)視圖view中,返回在目標(biāo)視圖view中的rect
- (CGRect)convertRect:(CGRect)rect toView:(UIView *)view;

// 將rect從view中轉(zhuǎn)換到當(dāng)前視圖中民褂,返回在當(dāng)前視圖中的rect
- (CGRect)convertRect:(CGRect)rect fromView:(UIView *)view;

layer也是一樣的茄菊,提供對(duì)應(yīng)的接口:

- (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;

翻轉(zhuǎn)的幾何結(jié)構(gòu)

常規(guī)說(shuō)來(lái),在iOS上赊堪,一個(gè)圖層的position位于父圖層的左上角面殖,但是在Mac OS上,通常是位于左下角哭廉。Core Animation可以通過(guò) geometryFlipped 屬性來(lái)適配這兩種情況脊僚,它決定了一個(gè)圖層的坐標(biāo)是否相對(duì)于父圖層垂直翻轉(zhuǎn),是一個(gè) BOOL 類(lèi)型遵绰。在iOS上通過(guò)設(shè)置它為 YES意味著它的子圖層將會(huì)被垂直翻轉(zhuǎn)辽幌, 也就是將會(huì)沿著底部排版而不是通常的頂部(它的所有子圖層也同理,除非把它們 的 geometryFlipped 屬性也設(shè)為YES )椿访。


Z坐標(biāo)軸

UIView嚴(yán)格的二維坐標(biāo)系不同乌企,CALayer存在于一個(gè)三維空間當(dāng)中。除了我們已經(jīng)討論過(guò)的positionanchorPoint屬性之外成玫, CALayer 還有另外兩個(gè)屬性加酵,zPositionanchorPointZ,二者都是在Z軸上描述圖層位置的浮點(diǎn)類(lèi)型哭当。

注意這里并沒(méi)有更深的屬性來(lái)描述由寬和高做成的 bounds了猪腕,圖層是一個(gè)完全扁平的對(duì)象,你可以把它們想象成類(lèi)似于一頁(yè)二維的堅(jiān)硬的紙片钦勘,用膠水粘成一個(gè) 空洞陋葡,就像三維結(jié)構(gòu)的折紙一樣。

zPosition 屬性在大多數(shù)情況下其實(shí)并不常用彻采。在第五章脖岛,我們將會(huì)涉及 CATransform3D 朵栖,你會(huì)知道如何在三維空間移動(dòng)和旋轉(zhuǎn)圖層,除了做變換之 外柴梆,zPosition最實(shí)用的功能就是改變圖層的顯示順序了陨溅。

其實(shí)并不需要增加太多,視圖都非常地薄绍在,所以給zPosition提高一個(gè)像素就可以讓后面的視圖前置门扇,當(dāng)然0.1或者0.0001也能夠做到,但是最好不要這樣偿渡,因?yàn)楦↑c(diǎn)類(lèi)型四舍五入的計(jì)算可能會(huì)造成一些不便的麻煩臼寄。

self.greenView.layer.zPosition = 1.0f;

Hit Testing

CALayer并不關(guān)心任何響應(yīng)鏈?zhǔn)录圆荒苤苯犹幚碛|摸事件或者手勢(shì)溜宽。但是它有一系列的方法幫你處理事件:-containsPoint:和-hitTest:

  • containsPoint:接受一個(gè)在本圖層坐標(biāo)系下的CGPoint(注意如果不是相對(duì)于自身圖層的需要先轉(zhuǎn)換再傳入判斷)吉拳,如果這個(gè)點(diǎn)在圖層frame范圍內(nèi)就返回YES。

  • hitTest:方法同樣接受一個(gè)CGPoint類(lèi)型參數(shù)适揉,而不是BOOL類(lèi)型留攒,它返回圖層本身,或者包含這個(gè)坐標(biāo)點(diǎn)的葉子節(jié)點(diǎn)圖層嫉嘀。
    這樣就不用像使用-containsPoint:那樣遍歷子layer看point是不是被子layer contain炼邀,只需要看父layer hitTest返回的layer與哪個(gè)子layer相同即可。如果這個(gè)點(diǎn)在最外面圖層的范圍之外剪侮,則返回nil拭宁。

※ 注意哦:當(dāng)調(diào)用圖層的-hitTest:方法時(shí),測(cè)算的順序嚴(yán)格依賴(lài)于圖層樹(shù)當(dāng)中的圖層順序(和UIView的響應(yīng)鏈?zhǔn)录?lèi)似)瓣俯。之前提到的zPosition屬性可以明顯改變屏幕上圖層的順序宿百,但不能改變事件傳遞的順序魁蒜。

也就是如果我們手動(dòng)修改了后面layer的zPosition讓它到最前面染服,你點(diǎn)擊的時(shí)候可能hitTest返回的永遠(yuǎn)不會(huì)是這個(gè)看起來(lái)是最前面的layer景东。


自動(dòng)布局

view的UIViewAutoresizingMask類(lèi)型的一些常量辕羽,可以應(yīng)用于當(dāng)父視圖改變尺寸的時(shí)候虎敦,相應(yīng)view的frame也跟著更新的場(chǎng)景滔岳,但是對(duì)于CALayer并不適用声邦。

也就是說(shuō)如果有個(gè)view1包含view2驻仅,當(dāng)你改view1尺寸的時(shí)候其實(shí)view2也會(huì)變谅畅,但是如果是layer1包含layer2,那么layer1尺寸變化噪服,layer2并不會(huì)變毡泻。

但如果想隨意控制CALayer的布局,就需要手動(dòng)操作粘优。最簡(jiǎn)單的方法就是使用CALayerDelegate如下函數(shù):

- (void)layoutSublayersOfLayer:(CALayer *)layer;

當(dāng)layer的bounds發(fā)生改變仇味,或者layer的-setNeedsLayout 方法被調(diào)用的時(shí)候呻顽,這個(gè)函數(shù)將會(huì)被執(zhí)行。但是不能像UIView的autoresizingMask和constraints屬性做到自適應(yīng)屏幕旋轉(zhuǎn)丹墨。


視覺(jué)效果

圓角

CALayer有一個(gè)叫做conrnerRadius的屬性控制著圖層角的曲率廊遍。它是一個(gè)浮點(diǎn)數(shù),默認(rèn)為0(為0的時(shí)候就是直角)贩挣,但是你可以把它設(shè)置成任意值喉前。默認(rèn)情況下,這個(gè)曲率值只影響背景顏色而不影響背景圖片或是子圖層王财。不過(guò)卵迂,如果把masksToBounds設(shè)置成YES的話(huà),圖層里面的所有東西都會(huì)被截取绒净。

圖層邊框

borderWidth是以點(diǎn)為單位的定義邊框粗細(xì)的浮點(diǎn)數(shù)见咒,默認(rèn)為0。borderColor定義了邊框的顏色挂疆,默認(rèn)為黑色改览。

borderColor是CGColorRef類(lèi)型,而不是UIColor囱嫩,所以它不是Cocoa的內(nèi)置對(duì)象恃疯。

邊框是繪制在圖層邊界里面的,而且在所有子內(nèi)容之前墨闲,也在子圖層之前(最上面)今妄。邊框不會(huì)管你的內(nèi)容大小,只和layer的邊界有關(guān)鸳碧。

陰影
  • shadowOpacity是一個(gè)必須在0.0(不可見(jiàn))和1.0(完全不透明)之間的浮點(diǎn)數(shù)盾鳞。如果設(shè)置為1.0,將會(huì)顯示一個(gè)有輕微模糊的黑色陰影稍微在圖層之上瞻离。若要改動(dòng)陰影的表現(xiàn)腾仅,你可以使用CALayer的另外三個(gè)屬性:shadowColor,shadowOffset和shadowRadius套利。

  • shadowColor屬性控制著陰影的顏色推励,和borderColor和backgroundColor一樣,它的類(lèi)型也是CGColorRef肉迫。陰影默認(rèn)是黑色验辞。

  • shadowOffset屬性控制著陰影的方向和距離。它是一個(gè)CGSize的值喊衫,寬度控制這陰影橫向的位移跌造,高度控制著縱向的位移。shadowOffset的默認(rèn)值是 {0, -3},意即陰影相對(duì)于Y軸有3個(gè)點(diǎn)的向上位移壳贪。

  • shadowRadius屬性控制著陰影的模糊度陵珍,當(dāng)它的值是0的時(shí)候,陰影就和視圖一樣有一個(gè)非常確定的邊界線(xiàn)违施。當(dāng)值越來(lái)越大的時(shí)候互纯,邊界線(xiàn)看上去就會(huì)越來(lái)越模糊和自然。蘋(píng)果自家的應(yīng)用設(shè)計(jì)更偏向于自然的陰影磕蒲,所以一個(gè)非零值再合適不過(guò)了伟姐。

和圖層邊框不同,圖層的陰影繼承自?xún)?nèi)容的外形亿卤,而不是根據(jù)邊界和角半徑來(lái)確定愤兵。為了計(jì)算出陰影的形狀,Core Animation會(huì)將寄宿圖(包括子視圖排吴,如果有的話(huà))考慮在內(nèi)秆乳,然后通過(guò)這些來(lái)完美搭配圖層形狀從而創(chuàng)建一個(gè)陰影。

陰影

于是就有了maskToBounds和陰影的沖突啦
陰影通常就是在Layer的邊界之外钻哩,如果你開(kāi)啟了masksToBounds屬性屹堰,所有從圖層中突出來(lái)的內(nèi)容都會(huì)被才剪掉。

如果你想沿著內(nèi)容裁切街氢,你需要用到兩個(gè)圖層:一個(gè)只畫(huà)陰影的空的父圖層(也就是對(duì)mask后的內(nèi)容進(jìn)行陰影)扯键,和一個(gè)用masksToBounds裁剪內(nèi)容的子圖層。

shadowPath屬性

我們已經(jīng)知道圖層陰影并不總是方的珊肃,而是從圖層內(nèi)容的形狀繼承而來(lái)荣刑。這看上去不錯(cuò),但是實(shí)時(shí)計(jì)算陰影也是一個(gè)非常消耗資源的伦乔,尤其是圖層有多個(gè)子圖層厉亏,每個(gè)圖層還有一個(gè)有透明效果的寄宿圖的時(shí)候。

如果你事先知道你的陰影形狀會(huì)是什么樣子的烈和,你可以通過(guò)指定一個(gè)shadowPath來(lái)提高性能爱只。shadowPath是一個(gè)CGPathRef類(lèi)型(一個(gè)指向CGPath的指針)。CGPath是一個(gè)Core Graphics對(duì)象招刹,用來(lái)指定任意的一個(gè)矢量圖形恬试。我們可以通過(guò)這個(gè)屬性單獨(dú)于圖層形狀之外指定陰影的形狀。

如果是一個(gè)矩形或者是圓疯暑,用CGPath會(huì)相當(dāng)簡(jiǎn)單明了训柴。但是如果是更加復(fù)雜一點(diǎn)的圖形,UIBezierPath類(lèi)會(huì)更合適缰儿,它是一個(gè)有UIKit提供的在CGPath基礎(chǔ)上的OC包裝類(lèi)畦粮。

testView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 200, 200)];
[self.view addSubview:testView];

testView.layer.cornerRadius = 20;

testView.image = [UIImage imageNamed:@"li.jpg"];

//路徑陰影
float paintingWidth = testView.frame.size.width;
float paintingHeight = testView.frame.size.height;
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(-5, -5)];
//添加直線(xiàn)
[path addLineToPoint:CGPointMake(paintingWidth /2, -15)];
[path addLineToPoint:CGPointMake(paintingWidth +5, -5)];
[path addLineToPoint:CGPointMake(paintingWidth +15, paintingHeight /2)];
[path addLineToPoint:CGPointMake(paintingWidth +5, paintingHeight +5)];
[path addLineToPoint:CGPointMake(paintingWidth /2, paintingHeight +15)];
[path addLineToPoint:CGPointMake(-5, paintingHeight +5)];
[path addLineToPoint:CGPointMake(-15, paintingHeight /2)];
[path addLineToPoint:CGPointMake(-5, -5)];
//設(shè)置陰影路徑
testView.layer.shadowPath = path.CGPath;
testView.layer.shadowOpacity = 1;

testView.frame = CGRectMake(300, 300, 100, 100);
改變view的frame陰影沒(méi)變

這里我最開(kāi)始設(shè)定的testView.layer.shadowPath其實(shí)是正好包裹testView的散址,但是改變testView的frame以后雖然陰影跟著view移動(dòng)了乖阵,但是大小沒(méi)有變宣赔,也就是相對(duì)位置不會(huì)變,其實(shí)就是重新根據(jù)shadowPath繪制了一遍瞪浸,反正shadowPath是相對(duì)view自身坐標(biāo)系的儒将。

也就是說(shuō),當(dāng)我們給定shadowPath以后对蒲,內(nèi)容改變后view是不會(huì)自動(dòng)計(jì)算shadow了就钩蚊,如果有改變需要手動(dòng)重新設(shè)置shadowPath,但這個(gè)可以改善性能啊蹈矮。


layer的mask屬性:圖層蒙版

CALayer有一個(gè)屬性叫做mask砰逻,這個(gè)屬性本身就是個(gè)CALayer類(lèi)型,有和其他圖層一樣的繪制和布局屬性泛鸟。它類(lèi)似于一個(gè)子圖層蝠咆,相對(duì)于父圖層(即擁有該屬性的圖層)布局,但是它卻不是一個(gè)普通的子圖層北滥。不同于那些繪制在父圖層中的子圖層刚操,mask圖層定義了父圖層的部分可見(jiàn)區(qū)域。

mask圖層的Color屬性是無(wú)關(guān)緊要的再芋,真正重要的是圖層的輪廓菊霜。mask屬性就像是一個(gè)餅干切割機(jī),mask圖層實(shí)心的部分會(huì)被保留下來(lái)济赎,其他的則會(huì)被拋棄鉴逞。

layer的mask

CALayer蒙板圖層真正厲害的地方在于蒙板圖不局限于靜態(tài)圖。任何有圖層構(gòu)成的都可以作為mask屬性司训,這意味著你的蒙板可以通過(guò)代碼甚至是動(dòng)畫(huà)實(shí)時(shí)生成华蜒。

拉伸過(guò)濾

當(dāng)圖片需要顯示不同的大小的時(shí)候,有一種叫做拉伸過(guò)濾的算法就起到作用了豁遭。它作用于原圖的像素上并根據(jù)需要生成新的像素顯示在屏幕上叭喜。

事實(shí)上,重繪圖片大小也沒(méi)有一個(gè)統(tǒng)一的通用算法蓖谢。這取決于需要拉伸的內(nèi)容捂蕴,放大或是縮小的需求等這些因素。CALayer為此提供了三種拉伸過(guò)濾方法闪幽,他們是:

kCAFilterLinear
kCAFilterNearest
kCAFilterTrilinear
  • kCAFilterLinear過(guò)濾器采用雙線(xiàn)性濾波算法啥辨,它在大多數(shù)情況下都表現(xiàn)良好。雙線(xiàn)性濾波算法通過(guò)對(duì)多個(gè)像素取樣最終生成新的值盯腌,得到一個(gè)平滑的表現(xiàn)不錯(cuò)的拉伸溉知。但是當(dāng)放大倍數(shù)比較大的時(shí)候圖片就模糊不清了。

  • kCAFilterTrilinearkCAFilterLinear非常相似,大部分情況下二者都看不出來(lái)有什么差別级乍。但是舌劳,較雙線(xiàn)性濾波算法而言,三線(xiàn)性濾波算法存儲(chǔ)了多個(gè)大小情況下的圖片(也叫多重貼圖)玫荣,并三維取樣甚淡,同時(shí)結(jié)合大圖和小圖的存儲(chǔ)進(jìn)而得到最后的結(jié)果。

  • kCAFilterNearest是一種比較武斷的方法捅厂。從名字不難看出贯卦,這個(gè)算法(也叫最近過(guò)濾)就是取樣最近的單像素點(diǎn)而不管其他的顏色。這樣做非潮捍快撵割,也不會(huì)使圖片模糊。但是辙芍,最明顯的效果就是睁枕,會(huì)使得壓縮圖片更糟,圖片放大之后也顯得塊狀或是馬賽克嚴(yán)重沸手。

放大縮小的三種插值

但是有的時(shí)候外遇,如果是差異較大色塊,用線(xiàn)性插值可能會(huì)使邊緣顏色混合模糊契吉,但是nearest可以只保留一個(gè)跳仿,邊緣更清晰,如設(shè)置:

view.layer.magnificationFilter = kCAFilterNearest;
組透明

(這個(gè)問(wèn)題最新的iOS好像已經(jīng)沒(méi)有了捐晶,你設(shè)置了父view的透明度以后菲语,看起來(lái)不會(huì)出現(xiàn)父子view的顏色差,雖然子view沒(méi)有設(shè)置透明度惑灵,但和父view顏色一致)

UIView有一個(gè)叫做alpha的屬性來(lái)確定視圖的透明度山上。CALayer有一個(gè)等同的屬性叫做opacity,這兩個(gè)屬性都是影響子層級(jí)的英支。也就是說(shuō)佩憾,如果你給一個(gè)圖層設(shè)置了opacity屬性,那它的子圖層都會(huì)受此影響干花。

左邊是一個(gè)不透明的按鈕妄帘,右邊是50%透明度的相同按鈕。我們可以注意到池凄,里面的標(biāo)簽的輪廓跟按鈕的背景很不搭調(diào):


透明度問(wèn)題

這是由透明度的混合疊加造成的抡驼,當(dāng)你顯示一個(gè)50%透明度的圖層時(shí),圖層的每個(gè)像素都會(huì)一半顯示自己的顏色肿仑,另一半顯示圖層下面的顏色致盟。這是正常的透明度的表現(xiàn)碎税。但是如果圖層包含一個(gè)同樣顯示50%透明的子圖層時(shí),你所看到的視圖馏锡,50%來(lái)自子視圖雷蹂,25%來(lái)了圖層本身的顏色,另外的25%則來(lái)自背景色眷篇。

理想狀況下,當(dāng)你設(shè)置了一個(gè)圖層的透明度荔泳,你希望它包含的整個(gè)圖層樹(shù)像一個(gè)整體一樣的透明效果蕉饼。你可以通過(guò)設(shè)置Info.plist文件中的UIViewGroupOpacity為YES來(lái)達(dá)到這個(gè)效果,但是這個(gè)設(shè)置會(huì)影響到這個(gè)應(yīng)用玛歌,整個(gè)app可能會(huì)受到不良影響昧港。如果UIViewGroupOpacity并未設(shè)置,iOS 6和以前的版本會(huì)默認(rèn)為NO(也許以后的版本會(huì)有一些改變)支子。

另一個(gè)方法就是创肥,你可以設(shè)置CALayer的一個(gè)叫做shouldRasterize屬性來(lái)實(shí)現(xiàn)組透明的效果,如果它被設(shè)置為YES值朋,在應(yīng)用透明度之前叹侄,圖層及其子圖層都會(huì)被整合成一個(gè)整體的圖片,這樣就沒(méi)有透明度混合的問(wèn)題了昨登。(緩存bitmap)

為了啟用shouldRasterize屬性趾代,我們?cè)O(shè)置了圖層的rasterizationScale屬性。默認(rèn)情況下丰辣,所有圖層拉伸都是1.0撒强, 所以如果你使用了shouldRasterize屬性,你就要確保你設(shè)置了rasterizationScale屬性去匹配屏幕笙什,以防止出現(xiàn)Retina屏幕像素化的問(wèn)題飘哨。

這個(gè)屬性需要注意性能問(wèn)題哦。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末琐凭,一起剝皮案震驚了整個(gè)濱河市芽隆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌统屈,老刑警劉巖摆马,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異鸿吆,居然都是意外死亡囤采,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)惩淳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蕉毯,“玉大人乓搬,你說(shuō)我怎么就攤上這事〈海” “怎么了进肯?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)棉磨。 經(jīng)常有香客問(wèn)我江掩,道長(zhǎng),這世上最難降的妖魔是什么乘瓤? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任环形,我火速辦了婚禮,結(jié)果婚禮上衙傀,老公的妹妹穿的比我還像新娘抬吟。我一直安慰自己,他們只是感情好统抬,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布火本。 她就那樣靜靜地躺著,像睡著了一般聪建。 火紅的嫁衣襯著肌膚如雪钙畔。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,590評(píng)論 1 305
  • 那天金麸,我揣著相機(jī)與錄音刃鳄,去河邊找鬼。 笑死钱骂,一個(gè)胖子當(dāng)著我的面吹牛叔锐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播见秽,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼愉烙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了解取?” 一聲冷哼從身側(cè)響起步责,我...
    開(kāi)封第一講書(shū)人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎禀苦,沒(méi)想到半個(gè)月后蔓肯,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡振乏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年蔗包,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片慧邮。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡调限,死狀恐怖舟陆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情耻矮,我是刑警寧澤秦躯,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站裆装,受9級(jí)特大地震影響踱承,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜哨免,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一茎活、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧铁瞒,春花似錦妙色、人聲如沸桅滋。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)丐谋。三九已至芍碧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間号俐,已是汗流浹背泌豆。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吏饿,地道東北人踪危。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像猪落,于是被迫代替她去往敵國(guó)和親贞远。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容