原文出處:http://blog.csdn.net/zhz459880251/article/details/50470447
參考: 本文是學習https://zsisme.gitbooks.io/ios-/content/index.html后進行的整理, 更多詳細的內(nèi)容可以去看看
在iOS當中碱呼,所有的視圖都從一個叫做UIVIew的基類派生而來驮审,UIView可以處理觸摸事件,可以支持基于Core Graphics繪圖错沽,可以做仿射變換(例如旋轉或者縮放),或者簡單的類似于滑動或者漸變的動畫婴谱。
CALayer也是一些被層級關系樹管理的矩形塊商源,同樣也可以包含一些內(nèi)容(像圖片,文本或者背景色)歉摧,管理子圖層的位置。和UIView最大的不同是CALayer不處理用戶的交互。
每一個UIview都有一個CALayer實例的圖層屬性, 這些圖層才是真正用來在屏幕上顯示和做動畫叁温,UIView僅僅是對它的一個封裝再悼,提供了一些iOS類似于處理觸摸的具體功能,以及Core Animation底層方法的高級接口膝但。
UIView的高級API間接地使得動畫變得很簡單冲九。
但UIView還有沒有暴露出來的CALayer的功能:
陰影,圓角跟束,帶顏色的邊框
3D變換
非矩形范圍
透明遮罩
多級非線性動畫
創(chuàng)建layer的創(chuàng)建和創(chuàng)建View其實差不多
CALayer *blueLayer = [CALayer layer];
//1. 設置 frame
blueLayer.frame = CGRectMake(50.0f,50.0f,100.0f,100.0f);
//2. 設置背景色
blueLayer.backgroundColor = [UIColor blueColor].CGColor;
//添加到父layer上
[self.view.layer addSublayer:blueLayer];
1. contents: 寄宿圖, 類型被定義為id, 但是其實是CGImage類型的;
layer.contents= (__bridge id)image.CGImage;
注意: 如果使用ARC,__bridge沒必要使用
UIImage*image = [UIImageimageNamed:@"1.png"];
self.layerView.layer.contents= (id)image.CGImage;
這樣一個不需要UIImageView就能顯示圖片的額View就建立起來了.
2. contentGravity: 對contents中顯示的圖片伸縮處理
在UIImageView中如果圖片拉伸了, 解決方法就是把contentMode屬性設置成更合適的值莺奸,像這樣:
view.contentMode=UIViewContentModeScaleAspectFit;
CALayer與contentMode對應的屬性叫做contentsGravity,但是它是一個NSString類型冀宴。contentsGravity可選的常量值有以下一些:
kCAGravityCenter
kCAGravityTop
kCAGravityBottom
kCAGravityLeft
kCAGravityRight
kCAGravityTopLeft
kCAGravityTopRight
kCAGravityBottomLeft
kCAGravityBottomRight
kCAGravityResize
kCAGravityResizeAspect
kCAGravityResizeAspectFill
self.view.layer.contentsGravity= kCAGravityResizeAspect;
2. contentsScale: 寄宿圖的像素尺寸和視圖大小的比例, 默認為1.0;
如果設置了contentsGravity = kCAGravityResizeAspect屬性灭贷,說明寄宿圖已經(jīng)被拉伸以適應圖層的邊界, 所以再設置contentsScale對屏幕上的寄宿圖沒有影響.contentsGravity = kCAGravityCenter有很明顯的變化
如果contentsScale設置為1.0,將會以每個點1個像素繪制圖片花鹅,如果設置為2.0,則會以每個點2個像素繪制圖片枫浙,這就是我們熟知的Retina屏幕刨肃。
self.view.layer.contentsScale= [UIScreen mainScreen].scale;
3. maskToBounds: 超出邊界是否剪切, bool類型
UIView有一個叫做clipsToBounds的屬性可以用來決定是否顯示超出邊界的內(nèi)容,CALayer對應的屬性叫做masksToBounds
YES: 超出部分 不顯示
NO: 超出部分也顯示
CALayer的contentsRect屬性允許我們在圖層邊框里顯示寄宿圖的一個子域箩帚。
和bounds真友,frame不同,contentsRect不是按點來計算的紧帕,它使用了單位坐標盔然,單位坐標指定在0到1之間,是一個相對值(像素和點就是絕對值)是嗜。所以他們是相對與寄宿圖的尺寸的愈案。iOS使用了以下的坐標系統(tǒng):
點—— 在iOS和Mac OS中最常見的坐標體系。點就像是虛擬的像素鹅搪,也被稱作邏輯像素站绪。在標準設備上,一個點就是一個像素丽柿,但是在Retina設備上恢准,一個點等于2*2個像素。iOS用點作為屏幕的坐標測算體系就是為了在Retina設備和普通設備上能有一致的視覺效果甫题。
像素—— 物理像素坐標并不會用來屏幕布局馁筐,但是仍然與圖片有相對關系。UIImage是一個屏幕分辨率解決方案坠非,所以指定點來度量大小敏沉。但是一些底層的圖片表示如CGImage就會使用像素,所以你要清楚在Retina設備和普通設備上,他們表現(xiàn)出來了不同的大小赦抖。
單位—— 對于與圖片大小或是圖層邊界相關的顯示舱卡,單位坐標是一個方便的度量方式, 當大小改變的時候队萤,也不需要再次調(diào)整轮锥。單位坐標在OpenGL這種紋理坐標系統(tǒng)中用得很多,Core Animation中也用到了單位坐標要尔。
默認的contentsRect是{0, 0, 1, 1}舍杜,這意味著整個寄宿圖默認都是可見的,如果我們指定一個小一點的矩形赵辕,圖片就會被裁剪
默認的contentsRect是{0, 0, 1, 1}既绩,這意味著整個寄宿圖默認都是可見的,如果我們指定一個小一點的矩形还惠,圖片就會被裁剪
事實上給contentsRect設置一個負數(shù)的原點或是大于{1, 1}的尺寸也是可以的饲握。這種情況下,最外面的像素會被拉伸以填充剩下的區(qū)域蚕键。
contentsRect另一個重要用途:
拼合技術–圖片拼合后可以打包整合到一張大圖上一次性載入救欧。相比多次載入不同的圖片,這樣做能夠帶來很多方面的好處:內(nèi)存使用锣光,載入時間笆怠,渲染性能等等
規(guī)則很簡單:像平常一樣載入我們的大圖,然后把它賦值給四個獨立的圖層的contents誊爹,然后設置每個圖層的contentsRect來去掉我們不想顯示的部分蹬刷。
@interfaceViewController()
@property(nonatomic,weak)IBOutletUIView*coneView;
@property(nonatomic,weak)IBOutletUIView*shipView;
@property(nonatomic,weak)IBOutletUIView*iglooView;
@property(nonatomic,weak)IBOutletUIView*anchorView;
@end
@implementationViewController
- (void)viewDidLoad
{
[superviewDidLoad];//load sprite sheet
UIImage*image = [UIImageimageNamed:@"Sprites.png"];
//set igloo sprite
[selfaddSpriteImage:image withContentRect:CGRectMake(0,0,0.5,0.5) toLayer:self.iglooView.layer];
//set cone sprite
[selfaddSpriteImage:image withContentRect:CGRectMake(0.5,0,0.5,0.5) toLayer:self.coneView.layer];
//set anchor sprite
[selfaddSpriteImage:image withContentRect:CGRectMake(0,0.5,0.5,0.5) toLayer:self.anchorView.layer];
//set spaceship sprite
[selfaddSpriteImage:image withContentRect:CGRectMake(0.5,0.5,0.5,0.5) toLayer:self.shipView.layer];
}
- (void)addSpriteImage:(UIImage*)image withContentRect:(CGRect)rect toLayer:(CALayer *)layer//set image{
layer.contents= (__bridgeid)image.CGImage;
//scale contents to fit
layer.contentsGravity= kCAGravityResizeAspect;
//set contentsRect
layer.contentsRect= rect;
}@end
Mac上有一些商業(yè)軟件可以為你自動拼合圖片,這些工具自動生成一個包含拼合后的坐標的XML或者plist文件频丘,拼合圖片的使用大大簡化办成。這個文件可以和圖片一同載入,并給每個拼合的圖層設置contentsRect搂漠,這樣開發(fā)者就不用手動寫代碼來擺放位置了诈火。
5. contentsCenter: 與 UIImage里的-resizableImageWithCapInsets: 方法效果非常類似
用于layer邊界的拉伸
layer.contentsCenter=CGRectMake(0.25, 0.25, 0.5, 0.5)
7.conrnerRadius: 圓角弧度
默認情況下,這個曲率值只影響背景顏色而不影響背景圖片或是子圖層
這條線(也被稱作stroke)沿著圖層的bounds繪制状答,同時也包含圖層的角
9.borderColor: 邊框顏色, CGColorRef
CGColorRef屬性即便是強引用也只能通過assign關鍵字來聲明
borderColor定義了邊框的顏色冷守,默認為黑色
邊框是繪制在圖層邊界里面的,而且在所有子內(nèi)容之前惊科,也在子圖層之前
11. shadowOpacity: 必須在0.0(不可見)和1.0(完全不透明)之間的浮點數(shù)
使用CALayer的另外三個屬性:shadowColor拍摇,shadowOffset和shadowRadius。
和borderColor和backgroundColor一樣馆截,它的類型也是CGColorRef充活。陰影默認是黑色
13. shadowOffset: 屬性控制著陰影的方向和距離
它是一個CGSize的值蜂莉,寬度控制這陰影橫向的位移,高度控制著縱向的位移混卵。shadowOffset的默認值是 {0, -3}映穗,意即陰影相對于Y軸有3個點的向上位移。
當它的值是0的當值越來越大的時候幕随,邊界線看上去就會越來越模糊和自然蚁滋。蘋果自家的應用設計更偏向于自然的陰影,所以一個非零值再合適不過了赘淮。
shadowRadius屬性控制著陰影的模糊度辕录,當它的值是0的時候,陰影就和視圖一樣有一個非常確定的邊界線
我們已經(jīng)知道圖層陰影并不總是方的梢卸,而是從圖層內(nèi)容的形狀繼承而來
但是實時計算陰影也是一個非常消耗資源的走诞,尤其是圖層有多個子圖層,每個圖層還有一個有透明效果的寄宿圖的時候蛤高。
如果你事先知道你的陰影形狀會是什么樣子的蚣旱,你可以通過指定一個shadowPath來提高性能
如果是一個矩形或者是圓,用CGPath會相當簡單明了戴陡。但是如果是更加復雜一點的圖形塞绿,UIBezierPath類會更合適,它是一個由UIKit提供的在CGPath基礎上的Objective-C包裝類猜欺。
CAShapeLayer是一個通過矢量圖形而不是bitmap來繪制的圖層子類位隶。你指定諸如顏色和線寬等屬性拷窜,用CGPath來定義想要繪制的圖形开皿,最后CAShapeLayer就自動渲染出來了。
當然篮昧,你也可以用Core Graphics直接向原始的CALyer的內(nèi)容中繪制一個路徑赋荆,相比直下,使用CAShapeLayer有以下一些優(yōu)點:
渲染快速懊昨。CAShapeLayer使用了硬件加速窄潭,繪制同一圖形會比用Core Graphics快很多。
高效使用內(nèi)存酵颁。一個CAShapeLayer不需要像普通CALayer一樣創(chuàng)建一個寄宿圖形嫉你,所以無論有多大,都不會占用太多的內(nèi)存躏惋。
不會被圖層邊界剪裁掉幽污。一個CAShapeLayer可以在邊界之外繪制。你的圖層路徑不會像在使用Core Graphics的普通CALayer一樣被剪裁掉(如我們在第二章所見)簿姨。
不會出現(xiàn)像素化距误。當你給CAShapeLayer做3D變換時簸搞,它不像一個有寄宿圖的普通圖層一樣變得像素化。
屬性比如
lineWith(線寬准潭,用點表示單位)趁俊,
lineCap(線條結尾的樣子),
lineJoin(線條之間的結合點的樣子)刑然;
在下一篇時會詳細說明一些用法, 及一些事亻列,
Core Animation提供了一個CALayer的子類CATextLayer寺擂,它以圖層的形式包含了UILabel幾乎所有的繪制特性,并且額外提供了一些新的特性闰集。
CATextLayer也要比UILabel渲染得快得多
如果我們想以Retina的質(zhì)量來顯示文字沽讹,我們就得手動地設置CATextLayer的contentsScale屬性,如下:
textLayer.contentsScale = [UIScreen mainScreen].scale;
CAGradientLayer是用來生成兩種或更多顏色平滑漸變的武鲁。用Core Graphics復制一個CAGradientLayer并將內(nèi)容繪制到一個普通圖層的寄宿圖也是有可能的爽雄,但是CAGradientLayer的真正好處在于繪制使用了硬件加速。
有startPoint和endPoint屬性沐鼠,他們決定了漸變的方向挚瘟。這兩個參數(shù)是以單位坐標系進行的定義,所以左上角坐標是{0, 0}饲梭,右下角坐標是{1, 1}乘盖。
下一篇詳細介紹, 及一些事例
CAReplicatorLayer的目的是為了高效生成許多相似的圖層。它會繪制一個或多個圖層的子圖層憔涉,并在每個復制體上應用不同的變換订框。
instanceCount屬性指定了圖層需要重復多少次。
instanceTransform指定了一個CATransform3D3D變換(這種情況下兜叨,下一圖層的位移和旋轉將會移動到圓圈的下一個點)穿扳。
instanceBlueOffset和instanceGreenOffset: 逐步減少藍色和綠色通道
CAReplicatorLayer*layer = (CAReplicatorLayer *)self.layer;
layer.instanceCount =2;
//move reflection instance below original and flip vertically
CATransform3Dtransform =CATransform3DIdentity;
CGFloatverticalOffset =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;
6.CAScrollLayer
7.CATiledLayer
CATiledLayer為載入大圖造成的性能問題提供了一個解決方案:將大圖分解成小片然后將他們單獨按需載入
8.CAEmitterLayer
在iOS5中,蘋果引入了一個新的CALayer子類叫做CAEmitterLayer国旷。CAEmitterLayer是一個高性能的粒子引擎矛物,被用來創(chuàng)建實時例子動畫如:煙霧,火跪但,雨等等這些效果履羞。
9.AVPlayerLayer
它不是Core Animation框架的一部分(AV前綴看上去像),AVPlayerLayer是有別的框架(AVFoundation)提供的屡久,它和Core Animation緊密地結合在一起忆首,提供了一個CALayer子類來顯示自定義的內(nèi)容類型。
AVPlayerLayer是用來在iOS上播放視頻的被环。他是高級接口例如MPMoivePlayer的底層實現(xiàn)糙及,提供了顯示視頻的底層控制。AVPlayerLayer的使用相當簡單:你可以用+playerLayerWithPlayer:方法創(chuàng)建一個已經(jīng)綁定了視頻播放器的圖層蛤售,或者你可以先創(chuàng)建一個圖層丁鹉,然后用player屬性綁定一個AVPlayer實例妒潭。
NSURL*URL = [[NSBundlemainBundle]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];
我們用代碼創(chuàng)建了一個AVPlayerLayer,但是我們?nèi)匀话阉砑拥搅艘粋€容器視圖中揣钦,而不是直接在controller中的主視圖上添加雳灾。這樣其實是為了可以使用自動布局限制使得圖層在最中間;否則冯凹,一旦設備被旋轉了我們就要手動重新放置位置谎亩,因為Core Animation并不支持自動大小和自動布局
AVPlayerLayer是CALayer的子類,它繼承了父類的所有特性宇姚。我們并不會受限于要在一個矩形中播放視頻匈庭;圓角,有色邊框浑劳,蒙板阱持,陰影等效果
CALayer自定義繪制
給contents賦CGImage的值不是唯一的設置寄宿圖的方法。我們也可以直接用Core Graphics直接繪制寄宿圖魔熏。能夠通過繼承UIView并實現(xiàn)-drawRect:方法來自定義繪制衷咽。
-drawRect:方法沒有默認的實現(xiàn),因為對UIView來說蒜绽,寄宿圖并不是必須的镶骗,它不在意那到底是單調(diào)的顏色還是有一個圖片的實例。如果UIView檢測到-drawRect: 方法被調(diào)用了躲雅,它就會為視圖分配一個寄宿圖鼎姊,這個寄宿圖的像素尺寸等于視圖大小乘以 contentsScale的值
但是創(chuàng)建-drawRect:會造成CPU資源和內(nèi)存的浪費;
當視圖在屏幕上出現(xiàn)的時候 -drawRect:方法就會被自動調(diào)用。-drawRect:方法里面的代碼利用Core Graphics去繪制一個寄宿圖
UIView有三個比較重要的布局屬性:frame相赁,bounds和center相寇,CALayer對應地叫做frame,bounds和position噪生。為了能清楚區(qū)分裆赵,圖層用了“position”东囚,視圖用了“center”跺嗽,但是他們都代表同樣的值。
frame: 代表了圖層的外部坐標(也就是在父圖層上占據(jù)的空間)
bounds是內(nèi)部坐標({0, 0}通常是圖層的左上角)
center和position都代表了相對于父圖層anchorPoint(中心點)所在的位置
視圖的frame页藻,bounds和center屬性僅僅是存取方法桨嫁,當操縱視圖的frame,實際上是在改變位于視圖下方CALayer的frame份帐,不能夠獨立于圖層之外改變視圖的frame璃吧。
注意: 1. 對于視圖或者圖層來說,frame并不是一個非常清晰的屬性废境,它其實是一個虛擬屬性畜挨,是根據(jù)bounds筒繁,position和transform計算而來,所以當其中任何一個值發(fā)生改變巴元,frame都會變化毡咏。相反,改變frame的值同樣會影響到他們當中的值
2. 當對圖層做變換的時候逮刨,比如旋轉或者縮放呕缭,frame實際上代表了覆蓋在圖層旋轉之后的整個軸對齊的矩形區(qū)域,也就是說frame的寬高可能和bounds的寬高不再一致了
視圖的center屬性和圖層的position屬性都指定了anchorPoint相對于父圖層的位置修己。圖層的anchorPoint通過position來控制它的frame的位置恢总,你可以認為anchorPoint是用來移動圖層的把柄。
默認來說睬愤,anchorPoint位于圖層的中點片仿,所以圖層的將會以這個點為中心放置。anchorPoint屬性并沒有被UIView接口暴露出來尤辱,這也是視圖的position屬性被叫做“center”的原因滋戳。但是圖層的anchorPoint可以被移動,比如你可以把它置于圖層frame的左上角啥刻,于是圖層的內(nèi)容將會向右下角的position方向移動奸鸯,而不是居中了。
contentsRect和contentsCenter屬性類似可帽,anchorPoint用單位坐標來描述娄涩,也就是圖層的相對坐標,圖層左上角是{0, 0}映跟,右下角是{1, 1}蓄拣,因此默認坐標是{0.5, 0.5}。anchorPoint可以通過指定x和y值小于0或者大于1努隙,使它放置在圖層范圍之外球恤。
一個圖層的position依賴于它父圖層的bounds
CALayer給不同坐標系之間的圖層轉換提供了一些工具類方法:
- (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;
這些方法可以把定義在一個圖層坐標系下的點或者矩形轉換成另一個圖層坐標系下的點或者矩形.
和UIView嚴格的二維坐標系不同,CALayer存在于一個三維空間當中荸镊。除了position和anchorPoint屬性之外咽斧,還有zPosition和anchorPointZ,二者都是在Z軸上描述圖層位置的浮點類型躬存。
zPosition最實用的功能就是改變圖層的顯示順序了
給zPosition提高一個像素就可以改變視圖顯示順序,
self.greenView.layer.zPosition=1.0f;
Hit Testing
CALayer并不關心任何響應鏈事件张惹,所以不能直接處理觸摸事件或者手勢。但是它有一系列的方法幫你處理事件:
- containsPoint:和- hitTest:岭洲。
- containsPoint:接受一個在本圖層坐標系下的CGPoint宛逗,如果這個點在圖層frame范圍內(nèi)就返回YES。這需要把觸摸坐標轉換成每個圖層坐標系下的坐標
-hitTest:方法同樣接受一個CGPoint類型參數(shù)盾剩,它返回不是BOOL類型雷激,而是圖層本身替蔬,或者包含這個坐標點的葉子節(jié)點圖層。如果這個點在最外面圖層的范圍之外屎暇,則返回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];
}elseif(layer ==self.layerView.layer) {
[[[UIAlertView alloc] initWithTitle:@"Inside White Layer"
message:nil
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil] show];
}
}
注意: 當調(diào)用圖層的-hitTest:方法時,測算的順序嚴格依賴于圖層樹當中的圖層順序(和UIView處理事件類似)恭垦。之前提到的zPosition屬性可以明顯改變屏幕上圖層的順序快毛,但不能改變事件傳遞的順序。
這意味著如果改變了圖層的z軸順序番挺,你會發(fā)現(xiàn)將不能夠檢測到最前方的視圖點擊事件唠帝,這是因為被另一個圖層遮蓋住了,雖然它的zPosition值較小玄柏,但是在圖層樹中的順序靠前襟衰。
當使用視圖的時候,可以充分利用UIView類接口暴露出來的UIViewAutoresizingMask和NSLayoutConstraintAPI
通過masksToBounds屬性粪摘,我們可以沿邊界裁剪圖形瀑晒;通過cornerRadius屬性,我們還可以設定一個圓角徘意。但是有時候你希望展現(xiàn)的內(nèi)容不是在一個矩形或圓角矩形
CALayer有一個屬性叫做mask可以解決這個問題
這個屬性本身就是個CALayer類型苔悦,有和其他圖層一樣的繪制和布局屬性。它類似于一個子圖層椎咧,相對于父圖層(即擁有該屬性的圖層)布局玖详,但是它卻不是一個普通的子圖層。不同于那些繪制在父圖層中的子圖層勤讽,圖層定義了父圖層的部分可見區(qū)域
如果mask圖層比父圖層要小蟋座,只有在mask圖層里面的內(nèi)容才是它關心的,除此以外的一切都會被隱藏起來脚牍。
“仿射”的意思是無論變換矩陣用什么值向臀,圖層中平行的兩條線在變換之后任然保持平行
CGAffineTransformMakeRotation(CGFloat angle)
CGAffineTransformMakeScale(CGFloat sx, CGFloat sy)
CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty)
CGAffineTransform transform = CGAffineTransformMakeRotation(M_PI_4);
self.layerView.layer.affineTransform = transform;
3D變換
CATransform3DMakeRotation(CGFloat angle, CGFloat x, CGFloat y, CGFloat z)
CATransform3DMakeScale(CGFloat sx, CGFloat sy, CGFloat sz)
CATransform3DMakeTranslation(Gloat tx, CGFloat ty, CGFloat tz)
CATransform3D transform = CATransform3DMakeRotation(M_PI_4,0,1,0);
self.layerView.layer.transform = transform;