iOS -圖層樹視圖與層的關(guān)系

Core Animation其實(shí)是一個(gè)令人誤解的命名栈顷。你可能認(rèn)為它只是用來做動(dòng)畫的,但實(shí)際上它是從一個(gè)叫做Layer Kit這么一個(gè)不怎么和動(dòng)畫有關(guān)的名字演變而來涡扼,所以做動(dòng)畫這只是Core Animation特性的冰山一角亮蒋。Core Animation是一個(gè)復(fù)合引擎闻书,它的職責(zé)就是盡可能快地組合屏幕上不同的可視內(nèi)容楞黄,這個(gè)內(nèi)容是被分解成獨(dú)立的圖層兄裂,存儲(chǔ)在一個(gè)叫做圖層樹的體系之中句旱。于是這個(gè)樹形成了UIKit以及在iOS應(yīng)用程序當(dāng)中你所能在屏幕上看見的一切的基礎(chǔ)。

1晰奖、圖層與視圖谈撒。

如果你曾經(jīng)在iOS或者M(jìn)ac OS平臺(tái)上寫過應(yīng)用程序,你可能會(huì)對(duì)視圖的概念比較熟悉匾南。一個(gè)視圖就是在屏幕上顯示的一個(gè)矩形塊(比如圖片啃匿,文字或者視頻),它能夠攔截類似于鼠標(biāo)點(diǎn)擊或者觸摸手勢等用戶輸入蛆楞。視圖在層級(jí)關(guān)系中可以互相嵌套溯乒,一個(gè)視圖可以管理它的所有子視圖的位置。

在iOS當(dāng)中豹爹,所有的視圖都從一個(gè)叫做UIVIew的基類派生而來裆悄,UIView可以處理觸摸事件,可以支持基于Core Graphics繪圖臂聋,可以做仿射變換(例如旋轉(zhuǎn)或者縮放)光稼,或者簡單的類似于滑動(dòng)或者漸變的動(dòng)畫

CALayer類在概念上和UIView類似崖技,同樣也是一些被層級(jí)關(guān)系樹管理的矩形塊,同樣也可以包含一些內(nèi)容(像圖片钟哥,文本或者背景色)迎献,管理子圖層的位置。它們有一些方法和屬性用來做動(dòng)畫和變換腻贰。和UIView最大的不同是CALayer不處理用戶的交互吁恍。

CALayer并不清楚具體的響應(yīng)鏈(iOS通過視圖層級(jí)關(guān)系用來傳送觸摸事件的機(jī)制),于是它并不能夠響應(yīng)事件播演,即使它提供了一些方法來判斷是否一個(gè)觸點(diǎn)在圖層的范圍之內(nèi)(具體見第三章冀瓦,“圖層的幾何學(xué)”)


每一個(gè)UIview都有一個(gè)CALayer實(shí)例的圖層屬性,也就是所謂的backing layer写烤,視圖的職責(zé)就是創(chuàng)建并管理這個(gè)圖層翼闽,以確保當(dāng)子視圖在層級(jí)關(guān)系中添加或者被移除的時(shí)候,他們關(guān)聯(lián)的圖層也同樣對(duì)應(yīng)在層級(jí)關(guān)系樹當(dāng)中有相同的操作

實(shí)際上這些背后關(guān)聯(lián)的圖層才是真正用來在屏幕上顯示和做動(dòng)畫洲炊,UIView僅僅是對(duì)它的一個(gè)封裝感局,提供了一些iOS類似于處理觸摸的具體功能,以及Core Animation底層方法的高級(jí)接口

但是為什么iOS要基于UIView和CALayer提供兩個(gè)平行的層級(jí)關(guān)系呢暂衡?為什么不用一個(gè)簡單的層級(jí)來處理所有事情呢询微?原因在于要做職責(zé)分離,這樣也能避免很多重復(fù)代碼狂巢。在iOS和Mac OS兩個(gè)平臺(tái)上撑毛,事件和用戶交互有很多地方的不同,基于多點(diǎn)觸控的用戶界面和基于鼠標(biāo)鍵盤有著本質(zhì)的區(qū)別唧领,這就是為什么iOS有UIKit和UIView藻雌,但是Mac OS有AppKit和NSView的原因。他們功能上很相似斩个,但是在實(shí)現(xiàn)上有著顯著的區(qū)別胯杭。

1.2 圖層的能力

如果說CALayer是UIView內(nèi)部實(shí)現(xiàn)細(xì)節(jié),那我們?yōu)槭裁匆娴亓私馑厝唬刻O果當(dāng)然為我們提供了優(yōu)美簡潔的UIView接口歉摧,那么我們是否就沒必要直接去處理Core Animation的細(xì)節(jié)了呢?

某種意義上說的確是這樣腔呜,對(duì)一些簡單的需求來說叁温,我們確實(shí)沒必要處理CALayer,因?yàn)樘O果已經(jīng)通過UIView的高級(jí)API間接地使得動(dòng)畫變得很簡單核畴。

但是這種簡單會(huì)不可避免地帶來一些靈活上的缺陷膝但。如果你略微想在底層做一些改變,或者使用一些蘋果沒有在UIView上實(shí)現(xiàn)的接口功能谤草,這時(shí)除了介入Core Animation底層之外別無選擇跟束。

我們已經(jīng)證實(shí)了圖層不能像視圖那樣處理觸摸事件莺奸,那么他能做哪些視圖不能做的呢?這里有一些UIView沒有暴露出來的CALayer的功能:陰影冀宴,圓角灭贷,帶顏色的邊框;3D變換略贮;非矩形范圍甚疟,透明遮罩、多級(jí)非線性動(dòng)畫逃延。

一個(gè)視圖只有一個(gè)相關(guān)聯(lián)的圖層(自動(dòng)創(chuàng)建)览妖,同時(shí)它也可以支持添加無數(shù)多個(gè)子圖層,你可以顯示創(chuàng)建一個(gè)單獨(dú)的圖層揽祥,并且把它直接添加到視圖關(guān)聯(lián)圖層的子圖層讽膏。盡管可以這樣添加圖層,但往往我們只是見簡單地處理視圖拄丰,他們關(guān)聯(lián)的圖層并不需要額外地手動(dòng)添加子圖層府树。

使用圖層關(guān)聯(lián)的視圖而不是CALayer的好處在于,你能在使用所有CALayer底層特性的同時(shí)愈案,也可以使用UIView的高級(jí)API(比如自動(dòng)排版挺尾,布局和事件處理)

然而,當(dāng)滿足以下條件的時(shí)候站绪,你可能更需要使用CALayer而不是UIView:

1)、開發(fā)同時(shí)可以在Mac OS上運(yùn)行的跨平臺(tái)應(yīng)用

2). 使用多種CALayer的子類,并且不想創(chuàng)建額外的UIView去包封裝它們所有;

但是這些例子都很少見丽柿,總的來說恢准,處理視圖會(huì)比單獨(dú)處理圖層更加方便

使用圖層

在屏幕中創(chuàng)建一個(gè)UIView對(duì)象正方形的,我們要在這個(gè)view上添加一個(gè)藍(lán)色的色塊甫题。我們可以添加一個(gè)子view用代碼或者IB都OK馁筐。但是這里我們準(zhǔn)備使用CAlayer。如果要想用layer相關(guān)的接口和屬性需要添加QuartzCore框架坠非。

//create sublayer

CALayer *blueLayer = [CALayer layer];

blueLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);

blueLayer.backgroundColor = [UIColor blueColor].CGColor;

//add it to our view

[self.layerView.layer addSublayer:blueLayer];

每一個(gè)視圖只有一個(gè)相關(guān)聯(lián)的圖層敏沉,可以在這個(gè)自動(dòng)創(chuàng)建的圖層上添加無數(shù)多的字圖層。

寄宿圖

寄宿圖就是指在CAlayer中包含的圖炎码。

對(duì)寄宿圖的處理主要在layer的contents屬性上盟迟,可以參考文章:iOS-CAlayer之contents 屬性。給contents賦值CGImage并不是唯一的設(shè)置寄宿圖的方法潦闲。我們可以通過CoreGraphics直接繪制寄宿圖攒菠。可以通過重寫drawRect方法來繪制歉闰。

-drawRect:方法沒有默認(rèn)的實(shí)現(xiàn)辖众,因?yàn)閷?duì)UIView來說卓起,寄宿圖并不是必須的,它不在意是單調(diào)的顏色還是一個(gè)圖片的實(shí)例凹炸。如果UIView檢測到-drawRect:方法被調(diào)用了戏阅,它就會(huì)為視圖分配一個(gè)寄宿圖,這個(gè)寄宿圖的像素尺寸就是視圖大小乘以contentsScale啤它。如果你不需要激素圖奕筐,那就不要這個(gè)方法了,這會(huì)造成CPU資源和內(nèi)存的浪費(fèi)蚕键。這就是如果沒有自定義的繪制任務(wù)救欧,不要重寫-drawRect:方法。

? ? ?當(dāng)視圖在屏幕出現(xiàn)的時(shí)候-drawRect:方法就會(huì)自動(dòng)調(diào)用锣光。-drawRect:方法里的代碼利用coregraphics去繪制一個(gè)寄宿圖笆怠,然后內(nèi)容就會(huì)被緩存起來直到它需要被更新(通常是因?yàn)殚_發(fā)者調(diào)用-setNeedsDisplay方法,盡管影響到表現(xiàn)效果的屬性值被更改時(shí)誊爹,一些視圖類型會(huì)被自動(dòng)重繪蹬刷,如bounds屬性)。

CAlayer有一個(gè)可選的delegate屬性频丘,實(shí)現(xiàn)了CAlayerDelegate協(xié)議办成,當(dāng)CAlayer需要一個(gè)內(nèi)容特定的信息時(shí),就會(huì)從協(xié)議中請(qǐng)求搂漠。CAlayerDelegate是一個(gè)非正式協(xié)議迂卢,沒有CAlayerDelegate可以在類中引用。

當(dāng)被要求重繪時(shí)桐汤,CAlayer會(huì)請(qǐng)求它的代理給它一個(gè)寄宿圖來顯示而克。它通過下面這個(gè)方法做到的:

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

我們就可以在方法中直接設(shè)置contents屬性,如果代理不實(shí)現(xiàn)此方法怔毛,CAlayer的代理就會(huì)嘗試調(diào)用下面這個(gè)方法:

-(void)drawLayer:(CAlayer*)layer in Context:(CGContextRef)ctx;

下面利用CAlayerDelegate做一些繪圖工作员萍。

//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);

}

Tips:1、我們在blueLayer上顯式的調(diào)用-display拣度。不同于UIView碎绎,當(dāng)CAlayer顯示在屏幕上時(shí),CALayer不會(huì)自動(dòng)重繪它的內(nèi)容抗果。它把重繪的決定權(quán)交給了developer筋帖。

? ? ? ? ? ?2、盡管我們沒有用maskToBounds屬性窖张,繪制的那個(gè)圓仍然沿邊界被裁剪了幕随,這是因?yàn)楫?dāng)你使用CAlayerDelegate繪制激素圖的時(shí)候,比沒有對(duì)超出layer邊界的內(nèi)容提供繪制支持宿接。

現(xiàn)在我們理解并知道怎么使用CAlayerDelegate赘淮,但是除非你創(chuàng)建一個(gè)單獨(dú)的圖層辕录,你幾乎沒有機(jī)會(huì)用奧CAlayerDelegate協(xié)議。因?yàn)楫?dāng)UIView創(chuàng)建它自己的宿主layer的時(shí)候梢卸,它自動(dòng)的就把layer的delegate設(shè)置為它自己走诞,并提供了-displayer:的實(shí)現(xiàn)。

當(dāng)使用寄宿圖層的時(shí)候蛤高,你也不必實(shí)現(xiàn)CALayerDelegate的方法蚣旱。通常都是重寫-drawRect:方法,UIView就會(huì)幫你完成剩下的工作戴陡,包括重繪時(shí)候調(diào)用-display方法塞绿。

? ? ?這一小節(jié)介紹了寄宿圖和一些相關(guān)屬性。學(xué)到了如何顯示和放置圖片恤批,使用拼合技術(shù)來顯示异吻,以及用CAlayerDelegate 和coreGraphics繪制圖層的。

圖層幾何學(xué)

在上一節(jié)中喜庞,我們介紹了圖層背后的圖片诀浪,和一些控制圖層的坐標(biāo)和旋轉(zhuǎn)的屬性,現(xiàn)在我們要學(xué)習(xí)如何根據(jù)父圖層和兄弟圖層來控制位置和尺寸延都,如何管理圖層的幾何結(jié)構(gòu)雷猪,以及它們是如何被自動(dòng)調(diào)整和自動(dòng)布局影響的。

1晰房、UIView有三個(gè)比較重要的布局屬性:frame求摇,bounds,center殊者;CAlayer對(duì)應(yīng)的叫做frame月帝,bounds和position。為了區(qū)分清楚幽污,layer用position,Uiview用center它們都是代表同樣的值簿姨。

frame代表了圖層的外部坐標(biāo)也就是在父圖層上占據(jù)的空間距误,bounds時(shí)內(nèi)部坐標(biāo)({0,0}通常是圖層的左上角),center和position都代表了相對(duì)于父圖層anchorPoint:錨點(diǎn)所在的位置”馕唬現(xiàn)在把它想成圖層的重點(diǎn)就好准潭。

UIView和CAlayer的坐標(biāo)系

? ? ? ? 視圖的frame、bounds域仇、center屬性僅僅是存取方法刑然,當(dāng)操作視圖frame,實(shí)際上是在改變位于視圖下方CAlayer的frame暇务,不能獨(dú)立于圖層之外改變視圖的frame泼掠。

對(duì)于視圖或者圖層來說怔软,frame并不是一個(gè)非常清晰的屬性,他是一個(gè)虛擬的屬性择镇,是根據(jù)bounds挡逼,positon和transform計(jì)算而來,所以當(dāng)其中任何一餓值發(fā)生變化腻豌,frame都會(huì)變化家坎。相反,改變frame的值一樣會(huì)影響bounds吝梅、tranform的值虱疏。

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

旋轉(zhuǎn)一個(gè)視圖或者圖層之后的frame

上文提到過視圖的center和圖層的position都是指定了一個(gè)anchorPoint相對(duì)于父圖層的位置兜叨。圖層的anchorPoint通過position來控制它的frame的位置穿扳,可以認(rèn)為anchorPoint是用來移動(dòng)圖層的。

? ? 默認(rèn)來說国旷,anchorPoint位于圖層的中點(diǎn)矛物,所以圖層以這個(gè)點(diǎn)為中心放置。anchorPoint屬性并沒有被UIView接口暴露出來跪但,這也是視圖positon屬性被叫做center的原因履羞。但是圖層的anchorPoint,可以被移動(dòng)屡久,比如你把a(bǔ)nchorPoint設(shè)置位于圖層的frame的左上角忆首,于是圖層的內(nèi)容將會(huì)想右下角的positon方法移動(dòng)而不是居中了。

改變了anchorPoint效果

和前面contentsRect以及contentsCenter類似被环,anchorPoint是用單位坐標(biāo)來描述的糙及,也就是圖岑的相對(duì)坐標(biāo),圖層左上角是(0筛欢,0)右下角是(1浸锨,1),因此默認(rèn)是(0.5版姑,0.5)柱搜。anchorPoint可以通過制定x、y小于0或者大于1剥险,使它放置在圖層范圍外聪蘸。 ?

? ? 在圖中我們可以看到當(dāng)anchorPoint變化時(shí)候,postion屬性保持固定值并沒有變,但是frame卻移動(dòng)了健爬。什么時(shí)候需要改變anchorPoint呢控乾?我們舉例說明。創(chuàng)建一個(gè)模擬鬧鐘的項(xiàng)目浑劳。

鐘面和三個(gè)指針

鬧鐘的組件通過IB來排列阱持,這些圖片嵌套在一個(gè)容器視圖中,并且自動(dòng)調(diào)整和自動(dòng)布局都被禁用了魔熏。這是因?yàn)樽詣?dòng)調(diào)整會(huì)影響到視圖的frame衷咽,當(dāng)視圖旋轉(zhuǎn)時(shí)候,frame是會(huì)發(fā)生變化的蒜绽,這將會(huì)導(dǎo)致一些布局上的失靈镶骗。

鐘面和指針的布局

我們用NSTimer來更新鬧鐘,使用視圖的transform屬性來旋轉(zhuǎn)鐘表躲雅。

@property (nonatomic, weak) IBOutlet UIImageView *hourHand;

@property (nonatomic, weak) IBOutlet UIImageView *minuteHand;

@property (nonatomic, weak) IBOutlet UIImageView *secondHand;

@property (nonatomic, weak) NSTimer *timer;

- (void)viewDidLoad

{

[super viewDidLoad];

//start timer

self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(tick) userInfo:nil repeats:YES];

//set initial hand positions

[self tick];

}

- (void)tick

{

//convert time to hours, minutes and seconds

NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];

NSUInteger units = NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;

NSDateComponents *components = [calendar components:units fromDate:[NSDate date]];

CGFloat hoursAngle = (components.hour / 12.0) * M_PI * 2.0;

//calculate hour hand angle //calculate minute hand angle

CGFloat minsAngle = (components.minute / 60.0) * M_PI * 2.0;

//calculate second hand angle

CGFloat secsAngle = (components.second / 60.0) * M_PI * 2.0;

//rotate hands

self.hourHand.transform = CGAffineTransformMakeRotation(hoursAngle);

self.minuteHand.transform = CGAffineTransformMakeRotation(minsAngle);

self.secondHand.transform = CGAffineTransformMakeRotation(secsAngle);

}

運(yùn)行項(xiàng)目看起來有點(diǎn)兒奇怪鼎姊,因?yàn)殓姳淼闹羔樤趪@中心旋轉(zhuǎn),這并不是是想要的相赁。

鐘面和不對(duì)齊的鐘指針

怎么解決這個(gè)問題呢相寇?可以在圖片的末尾添加一個(gè)透明空間,但是這樣會(huì)讓圖片變大钮科,也會(huì)消耗更多內(nèi)存唤衫。更好的方案是使用anchorPoint屬性,我們在viewDidload中添加幾行代碼讓鐘表每個(gè)指針的anchorPoint做一些平移

self.secondHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f);

self.minuteHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f);

self.hourHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f);

指針向上anchorPoint向下移動(dòng)绵脯。最后效果如下:

調(diào)整后的鐘表圖片

個(gè)人感覺anchorPoint就是圖層旋轉(zhuǎn)時(shí)候的旋轉(zhuǎn)軸佳励。

坐標(biāo)系

和視圖一樣,圖層在圖層數(shù)當(dāng)中也是相對(duì)于父圖層按層級(jí)關(guān)系放置蛆挫,一個(gè)圖層的position依賴于它父圖層的bounds赃承,如果父圖層發(fā)生了移動(dòng),它的所有子圖層也會(huì)跟著移動(dòng)悴侵。但是有時(shí)候你需要知道一個(gè)圖層的絕對(duì)位置瞧剖,或者是相對(duì)于另一個(gè)圖層的位置,而不是它當(dāng)前父圖層的位置可免。CAlayer提供了一些轉(zhuǎn)換工具類方法筒繁,這些以conver開頭的方法可以把定義在一個(gè)圖層坐標(biāo)系下的點(diǎn)或者矩形轉(zhuǎn)換成另一個(gè)圖層坐標(biāo)系下的點(diǎn)或者矩形。

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

通常說來巴元,在iOS上Original位于父圖層的左上角,但在OSX上驮宴,通常位于坐下角逮刨。CoreAnimation可以通過geometryFlipped屬性來適配這兩種情況,它決定了一個(gè)圖層的坐標(biāo)是否相對(duì)于父圖層垂直翻轉(zhuǎn),是一個(gè)Bool類型修己。在iOS上通過設(shè)置它為YES意味著它的子圖層將會(huì)被垂直翻轉(zhuǎn)恢总,也就是將會(huì)沿著地步排版而不是通暢的頂部。

Z坐標(biāo)軸

和UIView嚴(yán)格的二維坐標(biāo)系不同睬愤,CAlayer存在于一個(gè)三維空間中片仿。除了我們討論過的position和anchorPoint屬性外,CAlayer還有另外兩個(gè)屬性尤辱,zPosition和anchorPointZ砂豌,二者都是在z軸上描述圖層位置的浮點(diǎn)類型。

zPosition屬性在大多數(shù)情況下并不常用光督。在涉及CATransform3D阳距,在三維空間移動(dòng)和旋轉(zhuǎn)圖層會(huì)用到,除此最實(shí)用的功能就是改變圖層的顯示順序了结借。通常筐摘,圖層是根據(jù)它們子圖層的sublayers出現(xiàn)的順序來繪制的,這就是所謂的畫家算法-就像一個(gè)畫家在墻上作畫船老,后繪制的圖層灰遮蓋住之前的圖層咖熟,但是通過增加圖層的zPositon,就可以把圖層向相機(jī)方向前置柳畔,于是它就在所有其他圖層的前面了馍管。這里的相機(jī)實(shí)際上是相對(duì)于用戶的視角,這里和iPhone背面的內(nèi)置相機(jī)沒有任何關(guān)系荸镊。

//move the green view zPosition nearer to the camera

self.greenView.layer.zPosition = 1.0f;

就可以改變圖層的前后順序了咽斧。

Hit Testing

在開始圖層樹證實(shí)了最好使用圖層相關(guān)的視圖,而不是創(chuàng)建獨(dú)立的圖層關(guān)系躬存。其中一個(gè)原因就是要額外處理復(fù)雜的觸摸事件张惹。

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

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event

{

//get touch position relative to main view

CGPoint point = [[touches anyObject] locationInView:self.view];

//convert point to the white layer's coordinates

point = [self.layerView.layer convertPoint:point fromLayer:self.view.layer];

//get layer using containsPoint:

if ([self.layerView.layer containsPoint:point]) {}

}

-hitTest:方法同樣接受一個(gè)CGPoint類型參數(shù)宛逗,它返回的不是Bool類型,它返回的是圖層本身盾剩,或者包含這個(gè)坐標(biāo)點(diǎn)的葉子節(jié)點(diǎn)圖層雷激。如果這個(gè)點(diǎn)在最外層的范圍之外,則返回nil告私,使用如下

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event

{

//get touch position

CGPoint point = [[touches anyObject] locationInView:self.view];

//get touched layer

CALayer *layer = [self.layerView.layer hitTest:point];

//get layer using hitTest

if (layer == self.blueLayer) {

[[[UIAlertView alloc] initWithTitle:@"Inside Blue Layer"

message:nil

delegate:nil

cancelButtonTitle:@"OK"

otherButtonTitles:nil] show];

} else if (layer == self.layerView.layer) {

[[[UIAlertView alloc] initWithTitle:@"Inside White Layer"

message:nil

delegate:nil

cancelButtonTitle:@"OK"

otherButtonTitles:nil] show];

}

}

Notes:當(dāng)調(diào)用圖層的-hitTest:方法時(shí)屎暇,測試的順序嚴(yán)格按照?qǐng)D層樹當(dāng)中圖層的順序(和UIView處理事件類似)。之前說的zPosition屬性可以明顯的改變屏幕上圖層的順序驻粟,但不能改變事件傳遞的順序根悼。這意味著如果改變了圖層的zPosition,你會(huì)發(fā)現(xiàn)獎(jiǎng)不能檢測到最前方的視圖點(diǎn)擊事件,這是因?yàn)楸涣硪粋€(gè)圖層遮蓋住了挤巡,雖然前面的圖層zPosition值較小剩彬,但是在圖層順序中靠前。

自動(dòng)布局

你可能用過UIViewAutoresizingMask類型的一些常量矿卑,應(yīng)用于當(dāng)父視圖改變尺寸的時(shí)候喉恋,相對(duì)應(yīng)UIView的frame也跟著更新的場景。當(dāng)使用視圖的時(shí)候母廷,可以充分利用UIView類接口暴露出來的UiViewAutoresizeingMask和NSlayoutConstraint API轻黑,但是如果隨意控制CALayer的布局,就需要手工操作徘意。最簡單的方法就是使用CALayerDelegate:

- (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)。

這也是最好使用視圖而不是單獨(dú)的圖層來構(gòu)建應(yīng)用的一個(gè)重要原因勤讽。

視覺效果

我們在前一節(jié)圖層幾何學(xué)中討論了圖層的frame蟋座,之前還討論了圖層的寄宿圖。但是圖層不僅僅可以時(shí)圖片或者顏色的容器脚牍,還有一系列內(nèi)建的特性使得創(chuàng)造美麗優(yōu)雅的令人深刻的界面元素稱為可能向臀。這一節(jié)我們學(xué)習(xí)能夠通過CAlayer屬性是心啊的視覺效果。

圓角

圓角矩形在iOS中比較流行诸狭。除了直接使用有圓角的原始圖外券膀,CAlayer的conrnerRadius屬性控制著圖層角的曲率。默認(rèn)情況下這個(gè)曲率值只影響背景顏色而不是背景圖片或者子圖層驯遇。不過芹彬,如果把maskTobounds設(shè)置為YES,圖層理的所有東西都會(huì)被截取叉庐。

self.layerView2.layer.masksToBounds = YES;

? ? ? CALayer另外兩個(gè)非常有用屬性就是borderWidth和borderColor舒帮。二者共同定義了圖層邊的繪制樣式。這條線(也被稱作stroke)沿著圖層的bounds繪制陡叠,同時(shí)也包含圖層的角玩郊。邊框是根據(jù)圖層邊界變化的,而不是圖層里面的內(nèi)容枉阵。

陰影

shadowOpacity屬性的值大于0译红,陰影就可以顯示在任意圖層之下。若要改動(dòng)陰影的表現(xiàn)兴溜,你可以使用CAlayer的另外三個(gè)屬性:shadowColor临庇、shadowOffset和shadowRadius反璃。shadowOffset屬性控制著陰影的方向和距離。它是一個(gè)CGSize的值假夺,默認(rèn)是{0,-3},陰影相對(duì)Y軸有3個(gè)點(diǎn)的向上位移,這個(gè)是因?yàn)镃Alayer的坐標(biāo)和視圖的坐標(biāo)是不同的斋攀,CAlayer的坐標(biāo)是在MacOS上使用的已卷。

shadowRadius屬性控制著陰影的模糊度,當(dāng)它值為0淳蔼,陰影就和視圖一樣有一個(gè)非常確定的邊界線侧蘸,當(dāng)值越大的時(shí)候,邊界線就越來越模糊和自然鹉梨。和圖層的邊框不同讳癌,圖層的陰影繼承自內(nèi)容的外形,而不是根據(jù)邊界和角半徑來確定存皂。為了計(jì)算出陰影的形狀晌坤,CoreAnimation會(huì)將寄宿圖考慮在內(nèi),然后通過這些完美搭配圖層來創(chuàng)建一個(gè)陰影旦袋。陰影通常就是在layer的邊界之外骤菠,如果開啟了masksToBounds屬性,所有從圖層中突出來的內(nèi)容都會(huì)被剪掉疤孕。如果像沿著內(nèi)容裁切而且還有陰影商乎,就需要兩個(gè)圖層:一個(gè)花陰影的空的外圖層,和一個(gè)用masksToBounds裁剪內(nèi)容的內(nèi)圖層祭阀。

實(shí)時(shí)計(jì)算陰影是一個(gè)非常消耗資源的事情鹉戚,尤其是有多個(gè)子圖層的時(shí)候。如果事先知道陰影的形狀专控,可以通過shadowPath來提高性能抹凳。shadowPath是一個(gè)CGPathRef的類型。

//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);

圖層蒙版

? ? ? ? 通過maskToBounds屬性踩官,我們可以沿著邊界裁剪圖形却桶;通過cornerRadius屬性,我們可以設(shè)定一個(gè)圓角蔗牡。到那時(shí)有時(shí)候你希望展示的內(nèi)容不實(shí)載一個(gè)矩形或者圓角的矩形颖系。比如,展示一個(gè)有星形框架的圖片辩越,或者讓一些古卷文字慢慢漸變成背景色嘁扼,而不是一個(gè)突兀的邊界。

? ? ? 使用一個(gè)32位的有alpha通道的png圖片通常是創(chuàng)建一個(gè)非矩形視圖最方便的方法黔攒,可以通過指定一個(gè)透明蒙版來實(shí)現(xiàn)趁啸。但是這個(gè)方法不能讓我們通過代碼來實(shí)現(xiàn)蒙版强缘,也不能讓子圖層或者子視圖裁剪成同樣的形狀。

CAlayer有一個(gè)屬性mask可以解決這個(gè)問題不傅,這個(gè)屬性本身就是一個(gè)CAlayer類型旅掂,有和其他圖層一樣繪制和布局的屬性。它類似一個(gè)子圖層访娶,相對(duì)于父圖層布局商虐,但是它卻不是一個(gè)普通的子圖層。不同于那些繪制在父圖層中的子圖層崖疤,mask圖層定義了父圖層的部分可見區(qū)域秘车。

mask圖層的color屬性是無關(guān)要緊的,真正重要的是圖層的輪廓劫哼。mask屬性就像一個(gè)餅干切割機(jī)叮趴,mask圖層實(shí)心的部分會(huì)被保留,其它被舍棄权烧。

如果mask圖層比父圖層小眯亦,只有在mask圖層里面的內(nèi)容才顯示

圖片和蒙版圖層作用一起的效果

我們將演示下這個(gè)過程。

@property (nonatomic, weak) IBOutlet UIImageView *imageView;//圖層

- (void)viewDidLoad

{

[super viewDidLoad];

//create mask layer

CALayer *maskLayer = [CALayer layer];

maskLayer.frame = self.layerView.bounds;

//self.layerView是蒙版模型的view

UIImage *maskImage = [UIImage imageNamed:@"Cone.png"];

maskLayer.contents = (__bridge id)maskImage.CGImage;

//apply mask to image layer

self.imageView.layer.mask = maskLayer;

}

CAlayer蒙版不限于靜態(tài)圖豪嚎。任何有圖層構(gòu)成的都可以作為mask屬性搔驼,這意味著蒙版可以通過代碼甚至動(dòng)畫實(shí)時(shí)生成。

拉伸過濾

當(dāng)我們視圖顯示一個(gè)圖片的時(shí)候侈询,都應(yīng)該正確的顯示這個(gè)圖片舌涨。滿足如下條件:

1)能夠顯示最好的畫質(zhì),像素既沒有被壓塑也沒有被拉伸扔字。

2)能更好的使用內(nèi)存囊嘉。3)最好的性能表現(xiàn),CPU不需要為此額外計(jì)算革为。

不過有時(shí)候扭粱,顯示一個(gè)非真實(shí)大小的圖片也是我們需要的效果。比如一個(gè)頭像或者圖片的縮略圖震檩,再比如一個(gè)可以被拖拽和伸縮的大圖琢蛤。當(dāng)圖片需要顯示不同大小的時(shí)候,拉伸過濾的算法就起作用了抛虏,它作用于原圖的像素上并根據(jù)需要生成新的像素顯示在屏幕上博其。重繪圖片大小取決于需要拉伸的內(nèi)容,放大或者縮小的需求迂猴。CAlayer為此提供了三種拉伸過濾常量:kCAFilterLinear慕淡,kCAFilterNearest,kCAFilterTrilinear沸毁。

ninification縮小圖片和magnification放大圖片峰髓,默認(rèn)的過濾器都是kCAFillterLinear良蛮,這個(gè)過濾器采用雙線性濾波算法驯击,他在大多數(shù)情況下都表現(xiàn)良好字币。雙線性濾波算法通過多個(gè)像素取樣最終生成新的值覆积,得到一個(gè)平滑的表現(xiàn)不錯(cuò)的拉伸,但是當(dāng)放大倍數(shù)比較大的時(shí)候圖片就模糊了徐紧。

kCAFilterTrilinear和kCAFilterLinear非常相似个绍,大部分情況下二者都看不出區(qū)別,但是比較kCAFilterLinear浪汪,該三線性濾波算法存儲(chǔ)了多個(gè)大小情況下的圖片,并三位取樣凛虽,同時(shí)結(jié)合大圖和小圖的存儲(chǔ)進(jìn)而得到最后結(jié)果死遭。

總的來說,對(duì)于比較小的圖或者是差異特別明顯凯旋,極少斜線的大圖呀潭,kCAFilterNearest算法會(huì)保留這種差異明顯的特質(zhì)以呈現(xiàn)更好的結(jié)果。但是對(duì)于大多數(shù)的圖尤其是很多斜線或者曲線輪廓的圖片來說至非,kCAFilterNearest算法會(huì)導(dǎo)致更差的結(jié)果钠署,應(yīng)該用線性過濾算法

我們來驗(yàn)證一下,改動(dòng)前面鬧鐘的項(xiàng)目荒椭,用LCD風(fēng)格的數(shù)字顯示谐鼎。我們用簡單的像素字體創(chuàng)造數(shù)字顯示方式。

存儲(chǔ)在本地的數(shù)字圖片

用簡單的拼合技術(shù)來顯示LCD數(shù)字風(fēng)格的像素字體

用IB放置六個(gè)視圖趣惠,小時(shí)狸棍,分鐘,秒鐘各兩個(gè)味悄;

@interface ViewController ()

@property (nonatomic, strong) IBOutletCollection(UIView) NSArray *digitViews;

@property (nonatomic, weak) NSTimer *timer;

@end

- (void)viewDidLoad

{

[super viewDidLoad]; //get spritesheet image

UIImage *digits = [UIImage imageNamed:@"Digits.png"];

//set up digit views

for (UIView *view in self.digitViews) {

//set contents

view.layer.contents = (__bridge id)digits.CGImage;

view.layer.contentsRect = CGRectMake(0, 0, 0.1, 1.0);

view.layer.contentsGravity = kCAGravityResizeAspect;

}

//start timer

self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(tick) userInfo:nil repeats:YES];

//set initial clock time

[self tick];

}

- (void)setDigit:(NSInteger)digit forView:(UIView *)view

{//拼合圖層

//adjust contentsRect to select correct digit

view.layer.contentsRect = CGRectMake(digit * 0.1, 0, 0.1, 1.0);

}

- (void)tick

{

//convert time to hours, minutes and seconds

NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier: NSGregorianCalendar];

NSUInteger units = NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;

NSDateComponents *components = [calendar components:units fromDate:[NSDate date]];

//set hours

[self setDigit:components.hour / 10 forView:self.digitViews[0]];

[self setDigit:components.hour % 10 forView:self.digitViews[1]];

//set minutes

[self setDigit:components.minute / 10 forView:self.digitViews[2]];

[self setDigit:components.minute % 10 forView:self.digitViews[3]];

//set seconds

[self setDigit:components.second / 10 forView:self.digitViews[4]];

[self setDigit:components.second % 10 forView:self.digitViews[5]];

}

運(yùn)行的效果如下:

有點(diǎn)模糊草戈,是由默認(rèn)的kCAFilterLinear引起的

為了能夠顯示的更加清晰,通過上面我們知道小圖和沒有斜線的圖侍瑟,kCAFilterNearest顯示效果更好唐片。所以在for循環(huán)中加入:

view.layer.magnificationFilter = kCAFillterNearest;像素放大過濾器。


設(shè)置過濾之后的清晰顯示

透明度

? ? ? ? UIView有個(gè)alpha的屬性來去定視圖的透明度涨颜。CAlayer有一個(gè)等同的屬性叫做opacity费韭,這兩個(gè)屬性都是影響子層級(jí)的,也就是說咐低,如果你給一個(gè)圖層設(shè)置了opacity屬性揽思,那么它的子圖層都會(huì)受到影響。

iOS常見的做法是把一個(gè)控件的alpha設(shè)置為0.5见擦,使其看上去呈現(xiàn)不可用狀態(tài)钉汗。對(duì)于獨(dú)立的視圖來說還不錯(cuò)羹令,但是對(duì)一個(gè)有子視圖的控件就又點(diǎn)兒奇怪了。這是由透明度的混合疊加造成的损痰,當(dāng)你顯示一個(gè)50%透明度的圖層時(shí)福侈,圖層的每一個(gè)像素都會(huì)一半顯示自己的顏色另一半顯示下面的顏色。

? ? ? ? 理想狀況下卢未,當(dāng)你設(shè)置了一個(gè)圖層的透明度肪凛,我們希望它包含的整個(gè)圖層樹上一個(gè)整體一樣的透明效果。我們可以通過CAlayer的一個(gè)叫做shouldRasterize屬性來時(shí)現(xiàn)組透明的效果辽社,如果他被設(shè)置為YES伟墙,在應(yīng)用透明度之前,圖層及子圖層都會(huì)被整合成為一個(gè)整體的圖片滴铅,這樣就沒有透明度混合的問題了戳葵。

? ? ? ?為了啟用shouldRasterize屬性,我們設(shè)置了圖層的rasterizationScale屬性汉匙。默認(rèn)情況下拱烁,所有圖層都拉伸1.0,所以使用shouldRasterize屬性噩翠,確保設(shè)置了rasterizetionScale屬性去匹配屏幕戏自,防止出現(xiàn)Retina屏幕像素化的問題。

//創(chuàng)建一個(gè)不透明button伤锚,用來對(duì)比

UIButton *button1 = [self customButton];

button1.center = CGPointMake(50, 150);

[self.containerView addSubview:button1];

//創(chuàng)建一個(gè)變化的button

UIButton *button2 = [self customButton];

button2.center = CGPointMake(250, 150);

button2.alpha = 0.5;

[self.containerView addSubview:button2];

//enable rasterization for the translucent button

button2.layer.shouldRasterize = YES;//組透明

button2.layer.rasterizationScale = [UIScreen mainScreen].scale;變化的拉伸比例擅笔。

這樣button2像一個(gè)整體一樣被設(shè)置了透明度。

變換

1见芹、仿射變換

在圖層幾何學(xué)中剂娄,我們使用了UIView的transform屬性旋轉(zhuǎn)了鐘的指針,但是沒有解釋背后的運(yùn)作原理玄呛。實(shí)際上UIView的transform屬性是一個(gè)CGAffinetransfor的類型阅懦,用來在二維空間做旋轉(zhuǎn),縮放和平移徘铝。CGAffineTransform是一個(gè)可以和二維空間向量例如CGPoint做乘法的3x2矩陣耳胎。

用矩陣表示CGPoint和CGAffineTransform的關(guān)系

圖中顯示灰色的元素是為了滿足矩陣的乘法規(guī)則添加的信息,不改變運(yùn)算結(jié)果惕它。

當(dāng)圖層應(yīng)用變換矩陣怕午,圖層內(nèi)的一個(gè)點(diǎn)都被相應(yīng)的變換。CGAffineTransform中的仿射的意思是無論變換矩陣用什么值淹魄,圖層中平行的兩條線變換以后仍然保持平行郁惜。

CGAffineTransformMakeRotation(CGFloat angle)//旋轉(zhuǎn)

CGAffineTransformMakeScale(CGFloat sx, CGFloat sy)//縮放

CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty)//平移

UIView可以通過設(shè)置transform屬性做變換,但實(shí)際上它只是封裝了內(nèi)部圖層的變換甲锡。CAlayer也有一個(gè)transform屬性兆蕉,但是它的類型是CATransform3D羽戒,而不是平面CGAffineTransform,圖層對(duì)應(yīng)的屬性是affineTransform虎韵。例如:

CGAffineTransform transform = CGAffineTransformMakeRotation(M_PI_4);

self.layerView.layer.affineTransform = transform;

混合變換

CoreGraphics 提供一系列的函數(shù)可以在一個(gè)變換的基礎(chǔ)上做更深層次的變換易稠,例如可以做一個(gè)既要縮放又要旋轉(zhuǎn)的變換。

當(dāng)操縱一個(gè)變換時(shí)候包蓝,初始化一個(gè)單位矩陣是很重要的驶社,CGAffineTransformIdentity 提供一個(gè)方便的常量。

最后测萎,如果要混合兩個(gè)已經(jīng)存在的變換矩陣亡电,就可以使用如下方法,在兩個(gè)變換的基礎(chǔ)上創(chuàng)建一個(gè)新的變換:

CGAffineTransformconcat(CGAffineTransform t1硅瞧,t2)逊抡;

下面我們完成一個(gè)組合變換,先縮小50%零酪,再旋轉(zhuǎn)30度,最后像右平移200個(gè)像素拇勃。

//create a new transform初始化一個(gè)單位矩陣

CGAffineTransform transform = CGAffineTransformIdentity;

//scale by 50%

transform = CGAffineTransformScale(transform, 0.5, 0.5);

//rotate by 30 degrees

transform = CGAffineTransformRotate(transform, M_PI / 180.0 * 30.0);

//translate by 200 points

transform = CGAffineTransformTranslate(transform, 200, 0);

self.layerView.layer.affineTransform = transform;//在圖層上應(yīng)用變換四苇。

在實(shí)驗(yàn)中我們發(fā)現(xiàn),圖片向右邊發(fā)生了平移但是沒有200像素方咆,另外還有點(diǎn)兒向下平移月腋。原因在于我們按照順序做了變換,上一個(gè)變換的結(jié)果會(huì)影響之后的變換瓣赂。也就是說順序的改變造成的結(jié)果是不一樣的榆骚,若想分開效果就要用?

CGAffineTransformConcat去結(jié)合生成新的仿射。

3D變換

CG前綴告訴我們CGAffineTransform類型屬于CoreGraphics煌集,是一個(gè)2D繪圖API妓肢,前面我們提到zPositon屬性,可以讓圖層靠近或者用戶視角苫纤,transform屬性(CATransform3D)可以做到這點(diǎn)碉钠,讓圖層在3D空間內(nèi)移動(dòng)或者旋轉(zhuǎn)。

? ?和CGAffineTransform類似卷拘,CATransform3D也是一個(gè)矩陣喊废,不過是一個(gè)4x4的矩陣。

和CGAfineTransform矩陣類似栗弟,CoreAnimation提供了一系列的方法來創(chuàng)建和組合CATransform3D類型的矩陣污筷,和CoreGraphics類似,但是3D的平移和旋轉(zhuǎn)多了一個(gè)z參數(shù)乍赫,旋轉(zhuǎn)的函數(shù)除了angle之外瓣蛀,多了x陆蟆,y,z三個(gè)參數(shù)揪惦,分別決定了每個(gè)坐標(biāo)軸方向的旋轉(zhuǎn):

CATransform3DMakeRotation(CGFloat angle, CGFloat x, CGFloat y, CGFloat z)

CATransform3DMakeScale(CGFloat sx, CGFloat sy, CGFloat sz)

CATransform3DMakeTranslation(Gloat tx, CGFloat ty, CGFloat tz)

x遍搞、y、z器腋、軸溪猿,以及圍繞它們的方向

有圖可見,繞Z軸旋轉(zhuǎn)等同于之前二維空間的仿射旋轉(zhuǎn)纫塌,但是圍繞X軸和Y軸的旋轉(zhuǎn)就突破了屏幕的二維空間诊县,并且在用戶視角看來發(fā)生了傾斜。

我們做一個(gè)例子措左。CATransform3DMakeRotation對(duì)視圖內(nèi)的圖層繞Y軸做了45度的旋轉(zhuǎn)依痊,我們可以吧視圖向右傾斜,這樣看得更清晰怎披。

CATransform3D transform = CATransform3DMakeRotation(M_PI_4, 0, 1, 0);

self.layerView.layer.transform = transform;繞Y軸向右

看起來圖層并木有被旋轉(zhuǎn)胸嘁,僅僅是在水平方向上的一個(gè)壓縮,其實(shí)沒錯(cuò)凉逛,視圖看起來更窄是因?yàn)槲覀冊谝粋€(gè)斜向的視角看它性宏,不是透視。

? ? ? ?在真實(shí)世界中状飞,當(dāng)物體遠(yuǎn)離我們的時(shí)候毫胜,由于視角的原因看起來會(huì)變小,理論上說遠(yuǎn)離我們的視圖的邊要比靠近視角的邊跟短诬辈,但實(shí)際上并沒有發(fā)生酵使,而我們當(dāng)前的視角是等距離的,也就是在3D變換中任然保持平行焙糟,和之前提到的仿射變換類似

? ? ? 為了做一些修正口渔,我們需要引入投影變換(又稱作z變換)來對(duì)除了旋轉(zhuǎn)之外的變換矩陣做一些修改,Core Animation并沒有給我們提供設(shè)置透視變換的函數(shù)穿撮,因此我們需要手動(dòng)修改矩陣值搓劫,幸運(yùn)的是,很簡單:

CATransform3D的透視效果通過一個(gè)矩陣中一個(gè)很簡單的元素來控制:m34混巧,m34用于按比例縮放X和Y值來計(jì)算到底離視角多遠(yuǎn)枪向。

m34元素,用來做透視

m34的默認(rèn)值是0咧党,我們可以通過設(shè)置m34為-1.0/d來應(yīng)用透視效果秘蛔,d代表了,想象中視角相機(jī)和屏幕之間的距離,以像素為單位深员,這個(gè)距離不需要計(jì)算负蠕,估算一個(gè)就好,因?yàn)橐暯窍鄼C(jī)并不存在倦畅,可以根據(jù)效果自由決定遮糖。通常在500-1000之間,減少距離的值會(huì)增強(qiáng)透視效果叠赐。對(duì)視圖應(yīng)用透視的代碼如下:

//create a new transform

CATransform3D transform = CATransform3DIdentity;

//apply perspective

transform.m34 = - 1.0 / 500.0;

//rotate by 45 degrees along the Y axis

transform = CATransform3DRotate(transform, M_PI_4, 0, 1, 0);

//apply to layer

self.layerView.layer.transform = transform;

增加了透視感之后欲账,結(jié)果更像是在空間中3D翻轉(zhuǎn)了,更符合用戶視角芭概。

滅點(diǎn)

在透視角度繪圖的時(shí)候赛不,遠(yuǎn)離相機(jī)視角的物體會(huì)變小,當(dāng)遠(yuǎn)到一個(gè)極限距離的時(shí)候罢洲,它們可能就縮成了一個(gè)點(diǎn)踢故,于是所有物體最后都匯聚消失在同一個(gè)點(diǎn)。在現(xiàn)實(shí)中惹苗,這個(gè)點(diǎn)通常是視圖的中心殿较,于是為了在應(yīng)用中創(chuàng)建擬真效果的透視,這個(gè)點(diǎn)應(yīng)該聚在屏幕中點(diǎn)桩蓉,或者至少包含所有3D對(duì)象的視圖中點(diǎn)斜脂。

滅點(diǎn)

CoreAnimation定義了這個(gè)點(diǎn)位于變換圖層anchorPoint,通常初始化時(shí)候在圖層的中心触机,但并不是永遠(yuǎn)都在中心,如果手動(dòng)改變也是會(huì)變的玷或,比如鬧鐘的例子儡首。也就是說,當(dāng)圖層發(fā)生變換時(shí)候偏友,這個(gè)點(diǎn)位置不變的蔬胯,變換的軸線就是這個(gè)點(diǎn)。

當(dāng)改變一個(gè)圖層postion位他,也改變了它的滅點(diǎn)氛濒,做3D變換的時(shí)候要記住這點(diǎn)。當(dāng)視圖通過m34來讓它更加有3D效果鹅髓,應(yīng)該首先把它放倒屏幕中央舞竿,然后通過平移來把它移動(dòng)到指定位置,而不是直接改變它的position窿冯,這樣3D圖層都共享一個(gè)滅點(diǎn)骗奖。

如果有多個(gè)視圖或者圖層,每個(gè)都做3D變換,那就需要分別設(shè)置相同的m34值执桌,并且確保在變換之前都在屏幕中央享受同一個(gè)position鄙皇。CAlayer中又一個(gè)更好的方法。

CAlayer有個(gè)sublayerTransform屬性仰挣。它也是CATransform3D類型伴逸,它能影響到所有的子圖層。這意味著膘壶,我們可以一次性的對(duì)包含這些圖層的容器做變換错蝴,所有的子圖層都自動(dòng)繼承了這個(gè)變換方法。

通過一個(gè)地方設(shè)置透視變換的一個(gè)顯著優(yōu)勢就是香椎,滅點(diǎn)被設(shè)置在容器圖層中點(diǎn)漱竖,不要再對(duì)子圖層分別設(shè)置了。下面是一個(gè)例子

一個(gè)視圖容器內(nèi)并排放置兩個(gè)視圖

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *containerView;

@property (nonatomic, weak) IBOutlet UIView *layerView1;

@property (nonatomic, weak) IBOutlet UIView *layerView2;

@end

@implementation ViewController

- (void)viewDidLoad

{

[super viewDidLoad];

//apply perspective transform to container

CATransform3D perspective = CATransform3DIdentity;//初始化一個(gè)畜伐,position在中點(diǎn)

perspective.m34 = - 1.0 / 500.0;

//應(yīng)用了sublayerTranform屬性馍惹,保證子layer變換都在同一個(gè)滅點(diǎn)。

self.containerView.layer.sublayerTransform = perspective;

//rotate layerView1 by 45 degrees along the Y axis

CATransform3D transform1 = CATransform3DMakeRotation(M_PI_4, 0, 1, 0);

self.layerView1.layer.transform = transform1;

//rotate layerView2 by 45 degrees along the Y axis

CATransform3D transform2 = CATransform3DMakeRotation(-M_PI_4, 0, 1, 0);

self.layerView2.layer.transform = transform2;

}

相同的透視效果分別對(duì)視圖做變換

背面

上邊例子中我們都是在正面旋轉(zhuǎn)一定角度看到的玛界,如果旋轉(zhuǎn)180度呢万矾?圖層完全旋轉(zhuǎn)一個(gè)半圓,我們就從背面去看它了慎框。

視圖的背面良狈,一個(gè)鏡像對(duì)稱的圖片

但這并不是一個(gè)很好的特性,因?yàn)槿绻麍D層包含文本或者其他控件笨枯,用戶看到這些內(nèi)容的鏡像圖片會(huì)很奇怪薪丁,也會(huì)造成資源的浪費(fèi):想象一個(gè)不透明的固體立方體,既然永遠(yuǎn)都看不到這些圖層的背面馅精,為什么要浪費(fèi)GPU來繪制它們严嗜?

CAlayer有個(gè)doubleSided的屬性來控制圖層的背面是否要被繪制。這是一個(gè)BOOL類型洲敢,默認(rèn)為YES漫玄,設(shè)置為NO,那么當(dāng)圖層正面從相機(jī)視角消失的時(shí)候压彭,它背面不會(huì)被重繪睦优。

扁平化圖層

如果對(duì)包含已經(jīng)做過變換的圖層的圖層做反方向的變換會(huì)出現(xiàn)什么呢?

反方向變換的嵌套圖層

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *outerView;

@property (nonatomic, weak) IBOutlet UIView *innerView;

@end

@implementation ViewController

- (void)viewDidLoad

{

[super viewDidLoad];

//rotate the outer layer 45 degrees

CATransform3D outer = CATransform3DMakeRotation(M_PI_4, 0, 0, 1);

self.outerView.layer.transform = outer;

//rotate the inner layer -45 degrees

CATransform3D inner = CATransform3DMakeRotation(-M_PI_4, 0, 0, 1);

self.innerView.layer.transform = inner;

}

@end

在2D平面轉(zhuǎn)換結(jié)果和我們想的一樣壮不。

如果在3D情況下再試一個(gè)汗盘。修改代碼,讓內(nèi)外兩個(gè)視圖繞Y軸询一,而不是z軸衡未,再加上透視效果尸执。注意不能用sublayerTransform,因?yàn)閮?nèi)部圖層并不直接是容器圖層的子圖層缓醋,所以要分別對(duì)圖層設(shè)置透視變換如失。

//讓outer layer繞著Y軸45度

CATransform3D outer = CATransform3DIdentity;

outer.m34 = -1.0 / 500.0;

outer = CATransform3DRotate(outer, M_PI_4, 0, 1, 0);

self.outerView.layer.transform = outer;

//rotate the inner layer -45 degrees:內(nèi)圖層繞著Y軸-45度

CATransform3D inner = CATransform3DIdentity;

inner.m34 = -1.0 / 500.0;

inner = CATransform3DRotate(inner, -M_PI_4, 0, 1, 0);

self.innerView.layer.transform = inner;

我們預(yù)期效果如下:

繞Y軸做相反旋轉(zhuǎn)的預(yù)期結(jié)果

但我們看到的卻不是這樣的,我們看到的實(shí)際畫面是下面這樣送粱,內(nèi)部圖層仍然向左旋轉(zhuǎn)褪贵,并發(fā)生了扭曲:

繞Y軸做相反旋轉(zhuǎn)的真實(shí)結(jié)果

這是由于CA圖層盡管都存在于3D空間中,但是不同的圖層不都存在于同一個(gè)3D空間抗俄。每一個(gè)圖層的3D場景其實(shí)都是扁平化的脆丁。當(dāng)你從正面觀察一個(gè)圖層,看到的實(shí)際上由子圖層創(chuàng)建的想象出來的3D場景动雹,但當(dāng)我們傾斜這個(gè)圖層槽卫,你會(huì)發(fā)現(xiàn)這個(gè)3D場景僅僅是被繪制在圖層的表面上。

? ? ? ?類似的胰蝠,當(dāng)你在玩一個(gè)3D游戲歼培,實(shí)際上僅僅是把屏幕做了一次傾斜,或許在游戲中可以看見有一面墻在你面前茸塞,但是傾斜屏幕并不能夠看見墻里面的東西躲庄。所有場景里面繪制的東西并不會(huì)隨著你觀察它的角度改變而發(fā)生變化;圖層也是同樣的道理钾虐。

? ? ? ?這使得用Core Animation創(chuàng)建非常復(fù)雜的3D場景變得十分困難噪窘。你不能夠使用圖層樹去創(chuàng)建一個(gè)3D結(jié)構(gòu)的層級(jí)關(guān)系--在相同場景下的任何3D表面必須和同樣的圖層保持一致,這是因?yàn)槊總€(gè)的父視圖都把它的子視圖扁平化了效扫。

至少當(dāng)你用正常的CALayer的時(shí)候是這樣倔监,CALayer有一個(gè)叫做CATransformLayer的子類來解決這個(gè)問題。

另開一篇介紹這個(gè)“iOS-CATransformlayer”菌仁。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末浩习,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子掘托,更是在濱河造成了極大的恐慌,老刑警劉巖籍嘹,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闪盔,死亡現(xiàn)場離奇詭異,居然都是意外死亡辱士,警方通過查閱死者的電腦和手機(jī)泪掀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來颂碘,“玉大人异赫,你說我怎么就攤上這事。” “怎么了塔拳?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵鼠证,是天一觀的道長。 經(jīng)常有香客問我靠抑,道長量九,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任颂碧,我火速辦了婚禮荠列,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘载城。我一直安慰自己肌似,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布诉瓦。 她就那樣靜靜地躺著川队,像睡著了一般。 火紅的嫁衣襯著肌膚如雪垦搬。 梳的紋絲不亂的頭發(fā)上呼寸,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音猴贰,去河邊找鬼对雪。 笑死,一個(gè)胖子當(dāng)著我的面吹牛米绕,可吹牛的內(nèi)容都是我干的瑟捣。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼栅干,長吁一口氣:“原來是場噩夢啊……” “哼迈套!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起碱鳞,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤桑李,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后窿给,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贵白,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年崩泡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了禁荒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡角撞,死狀恐怖呛伴,靈堂內(nèi)的尸體忽然破棺而出勃痴,到底是詐尸還是另有隱情,我是刑警寧澤热康,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布沛申,位于F島的核電站,受9級(jí)特大地震影響褐隆,放射性物質(zhì)發(fā)生泄漏污它。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一庶弃、第九天 我趴在偏房一處隱蔽的房頂上張望衫贬。 院中可真熱鬧,春花似錦歇攻、人聲如沸固惯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽葬毫。三九已至,卻和暖如春屡穗,著一層夾襖步出監(jiān)牢的瞬間贴捡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國打工村砂, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留烂斋,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓础废,卻偏偏與公主長得像汛骂,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子评腺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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