原文出處:http://blog.csdn.net/zhz459880251/article/details/50470447
參考: 本文是學習https://zsisme.gitbooks.io/ios-/content/index.html后進行的整理, 更多詳細的內容可以去看看
在iOS當中,所有的視圖都從一個叫做UIVIew的基類派生而來,UIView可以處理觸摸事件,可以支持基于Core Graphics繪圖,可以做仿射變換(例如旋轉或者縮放),或者簡單的類似于滑動或者漸變的動畫。
CALayer也是一些被層級關系樹管理的矩形塊,同樣也可以包含一些內容(像圖片,文本或者背景色),管理子圖層的位置。和UIView最大的不同是CALayer不處理用戶的交互。
每一個UIview都有一個CALayer實例的圖層屬性, 這些圖層才是真正用來在屏幕上顯示和做動畫,UIView僅僅是對它的一個封裝,提供了一些iOS類似于處理觸摸的具體功能,以及Core Animation底層方法的高級接口。
UIView的高級API間接地使得動畫變得很簡單。
但UIView還有沒有暴露出來的CALayer的功能:
陰影,圓角,帶顏色的邊框
3D變換
非矩形范圍
透明遮罩
多級非線性動畫
創建layer的創建和創建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屬性,說明寄宿圖已經被拉伸以適應圖層的邊界, 所以再設置contentsScale對屏幕上的寄宿圖沒有影響.contentsGravity = kCAGravityCenter有很明顯的變化
如果contentsScale設置為1.0,將會以每個點1個像素繪制圖片,如果設置為2.0,則會以每個點2個像素繪制圖片,這就是我們熟知的Retina屏幕。
self.view.layer.contentsScale= [UIScreen mainScreen].scale;
3. maskToBounds: 超出邊界是否剪切, bool類型
UIView有一個叫做clipsToBounds的屬性可以用來決定是否顯示超出邊界的內容,CALayer對應的屬性叫做masksToBounds
YES: 超出部分 不顯示
NO: 超出部分也顯示
CALayer的contentsRect屬性允許我們在圖層邊框里顯示寄宿圖的一個子域。
和bounds,frame不同,contentsRect不是按點來計算的,它使用了單位坐標,單位坐標指定在0到1之間,是一個相對值(像素和點就是絕對值)。所以他們是相對與寄宿圖的尺寸的。iOS使用了以下的坐標系統:
點—— 在iOS和Mac OS中最常見的坐標體系。點就像是虛擬的像素,也被稱作邏輯像素。在標準設備上,一個點就是一個像素,但是在Retina設備上,一個點等于2*2個像素。iOS用點作為屏幕的坐標測算體系就是為了在Retina設備和普通設備上能有一致的視覺效果。
像素—— 物理像素坐標并不會用來屏幕布局,但是仍然與圖片有相對關系。UIImage是一個屏幕分辨率解決方案,所以指定點來度量大小。但是一些底層的圖片表示如CGImage就會使用像素,所以你要清楚在Retina設備和普通設備上,他們表現出來了不同的大小。
單位—— 對于與圖片大小或是圖層邊界相關的顯示,單位坐標是一個方便的度量方式, 當大小改變的時候,也不需要再次調整。單位坐標在OpenGL這種紋理坐標系統中用得很多,Core Animation中也用到了單位坐標。
默認的contentsRect是{0, 0, 1, 1},這意味著整個寄宿圖默認都是可見的,如果我們指定一個小一點的矩形,圖片就會被裁剪
默認的contentsRect是{0, 0, 1, 1},這意味著整個寄宿圖默認都是可見的,如果我們指定一個小一點的矩形,圖片就會被裁剪
事實上給contentsRect設置一個負數的原點或是大于{1, 1}的尺寸也是可以的。這種情況下,最外面的像素會被拉伸以填充剩下的區域。
contentsRect另一個重要用途:
拼合技術–圖片拼合后可以打包整合到一張大圖上一次性載入。相比多次載入不同的圖片,這樣做能夠帶來很多方面的好處:內存使用,載入時間,渲染性能等等
規則很簡單:像平常一樣載入我們的大圖,然后把它賦值給四個獨立的圖層的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上有一些商業軟件可以為你自動拼合圖片,這些工具自動生成一個包含拼合后的坐標的XML或者plist文件,拼合圖片的使用大大簡化。這個文件可以和圖片一同載入,并給每個拼合的圖層設置contentsRect,這樣開發者就不用手動寫代碼來擺放位置了。
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定義了邊框的顏色,默認為黑色
邊框是繪制在圖層邊界里面的,而且在所有子內容之前,也在子圖層之前
11. shadowOpacity: 必須在0.0(不可見)和1.0(完全不透明)之間的浮點數
使用CALayer的另外三個屬性:shadowColor,shadowOffset和shadowRadius。
和borderColor和backgroundColor一樣,它的類型也是CGColorRef。陰影默認是黑色
13. shadowOffset: 屬性控制著陰影的方向和距離
它是一個CGSize的值,寬度控制這陰影橫向的位移,高度控制著縱向的位移。shadowOffset的默認值是 {0, -3},意即陰影相對于Y軸有3個點的向上位移。
當它的值是0的當值越來越大的時候,邊界線看上去就會越來越模糊和自然。蘋果自家的應用設計更偏向于自然的陰影,所以一個非零值再合適不過了。
shadowRadius屬性控制著陰影的模糊度,當它的值是0的時候,陰影就和視圖一樣有一個非常確定的邊界線
我們已經知道圖層陰影并不總是方的,而是從圖層內容的形狀繼承而來
但是實時計算陰影也是一個非常消耗資源的,尤其是圖層有多個子圖層,每個圖層還有一個有透明效果的寄宿圖的時候。
如果你事先知道你的陰影形狀會是什么樣子的,你可以通過指定一個shadowPath來提高性能
如果是一個矩形或者是圓,用CGPath會相當簡單明了。但是如果是更加復雜一點的圖形,UIBezierPath類會更合適,它是一個由UIKit提供的在CGPath基礎上的Objective-C包裝類。
CAShapeLayer是一個通過矢量圖形而不是bitmap來繪制的圖層子類。你指定諸如顏色和線寬等屬性,用CGPath來定義想要繪制的圖形,最后CAShapeLayer就自動渲染出來了。
當然,你也可以用Core Graphics直接向原始的CALyer的內容中繪制一個路徑,相比直下,使用CAShapeLayer有以下一些優點:
渲染快速。CAShapeLayer使用了硬件加速,繪制同一圖形會比用Core Graphics快很多。
高效使用內存。一個CAShapeLayer不需要像普通CALayer一樣創建一個寄宿圖形,所以無論有多大,都不會占用太多的內存。
不會被圖層邊界剪裁掉。一個CAShapeLayer可以在邊界之外繪制。你的圖層路徑不會像在使用Core Graphics的普通CALayer一樣被剪裁掉(如我們在第二章所見)。
不會出現像素化。當你給CAShapeLayer做3D變換時,它不像一個有寄宿圖的普通圖層一樣變得像素化。
屬性比如
lineWith(線寬,用點表示單位),
lineCap(線條結尾的樣子),
lineJoin(線條之間的結合點的樣子);
在下一篇時會詳細說明一些用法, 及一些事亻列,
Core Animation提供了一個CALayer的子類CATextLayer,它以圖層的形式包含了UILabel幾乎所有的繪制特性,并且額外提供了一些新的特性。
CATextLayer也要比UILabel渲染得快得多
如果我們想以Retina的質量來顯示文字,我們就得手動地設置CATextLayer的contentsScale屬性,如下:
textLayer.contentsScale = [UIScreen mainScreen].scale;
CAGradientLayer是用來生成兩種或更多顏色平滑漸變的。用Core Graphics復制一個CAGradientLayer并將內容繪制到一個普通圖層的寄宿圖也是有可能的,但是CAGradientLayer的真正好處在于繪制使用了硬件加速。
有startPoint和endPoint屬性,他們決定了漸變的方向。這兩個參數是以單位坐標系進行的定義,所以左上角坐標是{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是一個高性能的粒子引擎,被用來創建實時例子動畫如:煙霧,火,雨等等這些效果。
9.AVPlayerLayer
它不是Core Animation框架的一部分(AV前綴看上去像),AVPlayerLayer是有別的框架(AVFoundation)提供的,它和Core Animation緊密地結合在一起,提供了一個CALayer子類來顯示自定義的內容類型。
AVPlayerLayer是用來在iOS上播放視頻的。他是高級接口例如MPMoivePlayer的底層實現,提供了顯示視頻的底層控制。AVPlayerLayer的使用相當簡單:你可以用+playerLayerWithPlayer:方法創建一個已經綁定了視頻播放器的圖層,或者你可以先創建一個圖層,然后用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];
我們用代碼創建了一個AVPlayerLayer,但是我們仍然把它添加到了一個容器視圖中,而不是直接在controller中的主視圖上添加。這樣其實是為了可以使用自動布局限制使得圖層在最中間;否則,一旦設備被旋轉了我們就要手動重新放置位置,因為Core Animation并不支持自動大小和自動布局
AVPlayerLayer是CALayer的子類,它繼承了父類的所有特性。我們并不會受限于要在一個矩形中播放視頻;圓角,有色邊框,蒙板,陰影等效果
CALayer自定義繪制
給contents賦CGImage的值不是唯一的設置寄宿圖的方法。我們也可以直接用Core Graphics直接繪制寄宿圖。能夠通過繼承UIView并實現-drawRect:方法來自定義繪制。
-drawRect:方法沒有默認的實現,因為對UIView來說,寄宿圖并不是必須的,它不在意那到底是單調的顏色還是有一個圖片的實例。如果UIView檢測到-drawRect: 方法被調用了,它就會為視圖分配一個寄宿圖,這個寄宿圖的像素尺寸等于視圖大小乘以 contentsScale的值
但是創建-drawRect:會造成CPU資源和內存的浪費;
當視圖在屏幕上出現的時候 -drawRect:方法就會被自動調用。-drawRect:方法里面的代碼利用Core Graphics去繪制一個寄宿圖
UIView有三個比較重要的布局屬性:frame,bounds和center,CALayer對應地叫做frame,bounds和position。為了能清楚區分,圖層用了“position”,視圖用了“center”,但是他們都代表同樣的值。
frame: 代表了圖層的外部坐標(也就是在父圖層上占據的空間)
bounds是內部坐標({0, 0}通常是圖層的左上角)
center和position都代表了相對于父圖層anchorPoint(中心點)所在的位置
視圖的frame,bounds和center屬性僅僅是存取方法,當操縱視圖的frame,實際上是在改變位于視圖下方CALayer的frame,不能夠獨立于圖層之外改變視圖的frame。
注意: 1. 對于視圖或者圖層來說,frame并不是一個非常清晰的屬性,它其實是一個虛擬屬性,是根據bounds,position和transform計算而來,所以當其中任何一個值發生改變,frame都會變化。相反,改變frame的值同樣會影響到他們當中的值
2. 當對圖層做變換的時候,比如旋轉或者縮放,frame實際上代表了覆蓋在圖層旋轉之后的整個軸對齊的矩形區域,也就是說frame的寬高可能和bounds的寬高不再一致了
視圖的center屬性和圖層的position屬性都指定了anchorPoint相對于父圖層的位置。圖層的anchorPoint通過position來控制它的frame的位置,你可以認為anchorPoint是用來移動圖層的把柄。
默認來說,anchorPoint位于圖層的中點,所以圖層的將會以這個點為中心放置。anchorPoint屬性并沒有被UIView接口暴露出來,這也是視圖的position屬性被叫做“center”的原因。但是圖層的anchorPoint可以被移動,比如你可以把它置于圖層frame的左上角,于是圖層的內容將會向右下角的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范圍內就返回YES。這需要把觸摸坐標轉換成每個圖層坐標系下的坐標
-hitTest:方法同樣接受一個CGPoint類型參數,它返回不是BOOL類型,而是圖層本身,或者包含這個坐標點的葉子節點圖層。如果這個點在最外面圖層的范圍之外,則返回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];
}
}
注意: 當調用圖層的-hitTest:方法時,測算的順序嚴格依賴于圖層樹當中的圖層順序(和UIView處理事件類似)。之前提到的zPosition屬性可以明顯改變屏幕上圖層的順序,但不能改變事件傳遞的順序。
這意味著如果改變了圖層的z軸順序,你會發現將不能夠檢測到最前方的視圖點擊事件,這是因為被另一個圖層遮蓋住了,雖然它的zPosition值較小,但是在圖層樹中的順序靠前。
當使用視圖的時候,可以充分利用UIView類接口暴露出來的UIViewAutoresizingMask和NSLayoutConstraintAPI
通過masksToBounds屬性,我們可以沿邊界裁剪圖形;通過cornerRadius屬性,我們還可以設定一個圓角。但是有時候你希望展現的內容不是在一個矩形或圓角矩形
CALayer有一個屬性叫做mask可以解決這個問題
這個屬性本身就是個CALayer類型,有和其他圖層一樣的繪制和布局屬性。它類似于一個子圖層,相對于父圖層(即擁有該屬性的圖層)布局,但是它卻不是一個普通的子圖層。不同于那些繪制在父圖層中的子圖層,圖層定義了父圖層的部分可見區域
如果mask圖層比父圖層要小,只有在mask圖層里面的內容才是它關心的,除此以外的一切都會被隱藏起來。
“仿射”的意思是無論變換矩陣用什么值,圖層中平行的兩條線在變換之后任然保持平行
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;