書籍是人類進步的階梯
總覽思維導圖
一、圖層樹
1.1.contents
簡介:(id類型),雖然是id類型但如果給contents賦的不是CGImage,那么得到的圖層將是空白的。
layer.contents = (__bridge id)image.CGImage;注意:在加載圖片時為了適應視圖,我們一般這么處理:
view.contentMode = UIViewContentModeScaleAspectFit;
但在CALayer與contentMode
對應的屬性叫做contentsGravity
:
self.layerView.layer.contentsGravity = kCAGravityResizeAspect;
1.2 contentsScale
- 寄宿圖的像素尺寸和視圖大小的比例,默認情況下它是一個值為1.0的浮點數:當用代碼來處理寄宿圖的時候,一定要記住要手動的設置圖層的contentsScale屬性,否則,你的圖片在Retina設備上就顯示得不正確啦。代碼如下:
layer.contentsScale = [UIScreen mainScreen].scale;
1.3 maskToBounds
- UIView有一個叫做clipsToBounds的屬性可以用來決定是否顯示超出邊界的內容,CALayer對應的屬性叫做masksToBounds
1.4 contentsRect
- 默認的contentsRect是{0, 0, 1, 1},這意味著整個寄宿圖默認都是可見的,如果設置{0,0,0.5,0.5},那只顯示左上角部分(整體的1/4部分)
1.5 contentsCenter
-
其實是一個CGRect,它定義了一個固定的邊框和一個
在圖層上可拉伸的區域。默認情況下,contentsCenter是{0, 0, 1, 1},這意
味著如果大小(由conttensGravity決定)改變了,那么寄宿圖將會均勻地拉
伸開。但是如果我們增加原點的值并減小尺寸。我們會在圖片的周圍創造
一個邊框。圖2.9展示了contentsCenter設置為{0.25, 0.25, 0.5, 0.5}的效果。
contentsCenter.png
1.6 -drawRect:方法:
- 當視圖在屏幕上出現的時候 -drawRect:方法就會被自動調用;
- 調用了-setNeedsDisplay方法時,-drawRect:方法會被調用;
二、圖層幾何學
2.1 frame、bounds、position
-
當對圖層做變換的時候,比如旋轉或者縮放,frame實際上代表了覆蓋在圖層旋轉之后的整個軸對齊的矩形區域,也就是說frame的寬高可能和bounds的寬高不再一致了
frame
2.2 錨點anchorPoint
- anchorPoint是用來移動圖層的把柄。anchorPoint用單位坐標來描述,也就是圖層的相對坐標,圖層左上角是{0, 0},右下角是{1, 1},因此默認坐標是{0.5, 0.5}
2.3zPosition
- 在大多數情況下其實并不常用。zPosition最實用的功能就是改變圖層的顯示順序了。通過增加圖層的zPosition,就可以把圖層向相機方向前置,于是它就在所有其他圖層的前面了(或者至少是小于它的zPosition值的圖層的前面)。
2.4 -containsPoint
- 接受一個在本圖層坐標系下的CGPoint,如果這個點在圖層frame范圍內就返回YES。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
CGPoint point = [[touches anyObject] locationInView:self.view];
point = [self.layerView.layer convertPoint:point fromLayer:self.view.layer];
//get layer using containsPoint:
if ([self.layerView.layer containsPoint:point]) {
//convert point to blueLayer’s coordinates
point = [self.blueLayer convertPoint:point fromLayer:self.layerView.layer];
if ([self.blueLayer containsPoint:point]) {
// your code
} else {
// your other code
}
}
}
2.5 -hitTest
- 方法接受一個CGPoint類型參數,而不是BOOL類型,它返回圖層本身,或者包含這個坐標點的葉子節點圖層:
- (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) {
// your code
} else if (layer == self.layerView.layer) {
// your other code
}
}
三、專用圖層CALayer
3.1 CAShapeLayer
- 使用CAShapeLayer的優點:
- 渲染快速。CAShapeLayer使用了硬件加速,繪制同一圖形會比用Core Graphics快很多;
- 高效使用內存。一個CAShapeLayer不需要像普通CALayer一樣創建一個寄宿圖形,所以無論有多大,都不會占用太多的內存;
- 不會被圖層邊界剪裁掉。一個CAShapeLayer可以在邊界之外繪制。
- 不會出現像素化。當你給CAShapeLayer做3D變換時,它不像一個有寄宿圖的普通圖層一樣變得像素化。
1.CAShapeLayer可以用來繪制所有能夠通過CGPath來表示的形狀。
2.CAShapeLayer屬性是CGPathRef類型
- 繪制圓角:( 需求:三個圓角,一個直角)
CGRect rect = CGRectMake(50, 50, 100, 100);
CGSize radii = CGSizeMake(20, 20);
UIRectCorner corners = UIRectCornerTopRight | UIRectCornerBottomRight | UIRectCornerBottomLeft;
//create path
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:corners cornerRadii:radii];
3.2 CATextLayer
- CATextLayer也要比UILabel渲染得快得多。很少有人知道在iOS 6及之前的版本,UILabel其實是通過WebKit來實現繪制的,這樣就造成了當有很多文字的時候就會有極大的性能壓力。
- CATextLayer使用了Core text,并且渲染得非常快。可以嘗試封裝一個CATextLayer用于替換UILabel
UILabel的替代品
- 使用CATextLayer來封裝一個UILabel的子類
#import "LayerLabel.h"
@implementation LayerLabel
+ (Class)layerClass
{
//this makes our label create a CATextLayer //instead of a regular CALayer for its backing layer
return [CATextLayer class];
}
- (CATextLayer *)textLayer
{
return (CATextLayer *)self.layer;
}
- (void)setUp
{
//set defaults from UILabel settings
self.text = self.text;
self.textColor = self.textColor;
self.font = self.font;
//we should really derive these from the UILabel settings too
//but that's complicated, so for now we'll just hard-code them
[self textLayer].alignmentMode = kCAAlignmentJustified;
[self textLayer].wrapped = YES;
[self.layer display];
}
- (id)initWithFrame:(CGRect)frame
{
//called when creating label programmatically
if (self = [super initWithFrame:frame]) {
[self setUp];
}
return self;
}
- (void)awakeFromNib
{
//called when creating label using Interface Builder
[self setUp];
}
- (void)setText:(NSString *)text
{
super.text = text;
//set layer text
[self textLayer].string = text;
}
- (void)setTextColor:(UIColor *)textColor
{
super.textColor = textColor;
//set layer text color
[self textLayer].foregroundColor = textColor.CGColor;
}
- (void)setFont:(UIFont *)font
{
super.font = font;
//set layer font
CFStringRef fontName = (__bridge CFStringRef)font.fontName;
CGFontRef fontRef = CGFontCreateWithFontName(fontName);
[self textLayer].font = fontRef;
[self textLayer].fontSize = font.pointSize;
CGFontRelease(fontRef);
}
@end
3.3 CATransformLayer
- CATransformLayer不同于普通的CALayer,因為它不能顯示它自己的內容。只有當存在了一個能作用域子圖層的變換它才真正存在。CATransformLayer并不平面化它的子圖層,所以它能夠用于構造一個層級的3D結構
3.4 CATiledLayer
- CATiledLayer為載入大圖造成的性能問題提供了一個解決方案:將大圖分解成小片然后將他們單獨按需載入
3.5 CAGradientLayer
- CAGradientLayer是用來生成兩種或更多顏色平滑漸變的。用Core Graphics復制一個CAGradientLayer并將內容繪制到一個普通圖層的寄宿圖也是有可能的,但是CAGradientLayer的真正好處在于繪制使用了硬件加速。
3.6 CAReplicatorLayer:(反射效果)
- CAReplicatorLayer的目的是為了高效生成許多相似的圖層。它會繪制一個或多個圖層的子圖層,并在每個復制體上應用不同的變換。
3.7 CAEmitterLayer(粒子、火焰特效)
- 簡介CAEmitterLayer是一個高性能的粒子引擎,被用來創建實時例子動畫如:煙霧,火,雨等等這些效果。
-
CAEmitterCell:
CAEmitterLayer看上去像是許多CAEmitterCell的容器,這些CAEmitierCell定義了一個例子效果。你將會為不同的例子效果定義一個或多個CAEmitterCell作為模版,同時CAEmitterLayer負責基于這些模版實例化一個粒子流。一個CAEmitterCell類似于一個CALayer:它有一個contents屬性可以定義為一個CGImage。
CAEMitterCell的屬性
- 這種粒子的某一屬性的初始值。比如,color屬性指定了一個可以混合圖片內容顏色的混合色。在示例中,我們將它設置為桔色。
- 例子某一屬性的變化范圍。比如emissionRange屬性的值是2π,這意味著例子可以從360度任意位置反射出來。如果指定一個小一些的值,就可以創造出一個圓錐形。
- 指定值在時間線上的變化。比如,在示例中,我們將alphaSpeed設置為-0.4,就是說例子的透明度每過一秒就是減少0.4,這樣就有發射出去之后逐漸消失的效果。
- preservesDepth:是否將3D例子系統平面化到一個圖層(默認值)或者可以在3D空間中混合其他的圖層。
- renderMode:控制著在視覺上粒子圖片是如何混合的。你可能已經注意到了示例中我們把它設置為kCAEmitterLayerAdditive,它實現了這樣一個效果:合并例子重疊部分的亮度使得看上去更亮。
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *containerView;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
CAEmitterLayer *emitter = [CAEmitterLayer layer];
emitter.frame = self.containerView.bounds;
[self.containerView.layer addSublayer:emitter];
//configure emitter
emitter.renderMode = kCAEmitterLayerAdditive;
emitter.emitterPosition = CGPointMake(emitter.frame.size.width / 2.0, emitter.frame.size.height / 2.0);
//create a particle template
CAEmitterCell *cell = [[CAEmitterCell alloc] init];
cell.contents = (__bridge id)[UIImage imageNamed:@"Spark.png"].CGImage;
cell.birthRate = 150;
cell.lifetime = 5.0;
cell.color = [UIColor colorWithRed:1 green:0.5 blue:0.1 alpha:1.0].CGColor;
cell.alphaSpeed = -0.4;
cell.velocity = 50;
cell.velocityRange = 50;
cell.emissionRange = M_PI * 2.0;
//add particle template to emitter
emitter.emitterCells = @[cell];
}
@end
3.8 CAEAGLLayer
- 用來顯示任意的OpenGL圖形,一般用不到。
3.9 AVPlayerLayer
- AVPlayerLayer是CALayer的子類,它繼承了父類的所有特性,主要用于視頻播放。
4.0 CAScrollLayer
- UIView中的UIScrollView的底層封裝。
四、視覺效果
4.1 圓角
- conrnerRadius:控制著圖層角的曲率。(只影響背景顏色而不影響背景圖片或是子圖層),一般和masksToBounds配合著使用。
- borderWidth:定義邊框粗細
- borderColor:邊框的顏色
4.2 陰影
- shadowOpacity:0.0(不可見)和1.0(完全不透明)之間的浮點數;
- shadowColor:控制陰影的顏色;
- shadowOffset:控制陰影的方向和距離。默認值是 {0, -3},意即陰影相對于Y軸有3個點的向上位移;
- shadowRadius:控制著陰影的模糊度,當值為0時,陰影和視圖有非常確定的邊界線。值越大,邊界線看上去就會越來越模糊和自然;
- shadowPath:一個CGPathRef類型(一個指向CGPath的指針)。我們可以通過這個屬性單獨于圖層形狀之外指定陰影的形狀;
- mask:mask圖層比父圖層要小,只有在mask圖層里面的內容才是它關心的,除此以外的一切都會被隱藏起來。代碼演示:
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIImageView *imageView;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//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;
}
@end
效果:
4.3拉伸過濾:
view.layer.magnificationFilter = kCAFilterNearest;
4.4 組透明
- 透明度會疊加,即當一個控件有子控件時,設置父控件的透明度(UIView對應alpha、CALayer對應opacity),子控件的透明度也會被影響。設置CALayer的一個叫做shouldRasterize屬性來實現組透明的效果,如果它被設置為YES,在應用透明度之前,圖層及其子圖層都會被整合成一個整體的圖片,這樣就沒有透明度混合的問題了:
view.layer.shouldRasterize = YES;
view.layer.rasterizationScale = [UIScreen mainScreen].scale;
五、變換
5.1 仿射變換CGAffineTransform(2D變換)
5.1.1 原理:
1. UIView的transform屬性是一個CGAffineTransform類型,用于在二維空間做旋轉,縮放和平移;
2. CALayer對應于UIView的transform屬性叫做affineTransform;
3. CALayer同樣也有一個transform屬性,但它的類型是CATransform3D。
5.1.2 主要方法:
CGAffineTransformMakeRotation(CGFloat angle); //旋轉
CGAffineTransformMakeScale(CGFloat sx, CGFloat sy); //縮放
CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty); //平移
5.1.3 混合變換:CGAffineTransformIdentity
// 需求:先縮小50%,再旋轉30度,最后向右移動200個像素
- (void)viewDidLoad
{
[super viewDidLoad];
//create a new transform
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);
//apply transform to layer
self.layerView.layer.affineTransform = transform;
}
3D變換CATransform3D
5.2.1 主要方法:
CATransform3DMakeRotation(CGFloat angle, CGFloat x, CGFloat y, CGFloat z); //旋轉
CATransform3DMakeScale(CGFloat sx, CGFloat sy, CGFloat sz); //縮放
CATransform3DMakeTranslation(Gloat tx, CGFloat ty, CGFloat tz); //平移
5.2.2 透視投影:m34
m34的默認值是0,我們可以通過設置m34為-1.0 / d來應用透視效果,d代表了想象中視角相機和屏幕之間的距離,通常500-1000就已經很好了。
5.2.3 滅點
當在透視角度繪圖的時候,遠離相機視角的物體將會變小變遠,當遠離到一個極限距離,它們可能就縮成了一個點,于是所有的物體最后都匯聚消失在同一個點(在現實中,這個點通常是視圖的中心)
- sublayerTransform:它也是CATransform3D類型,它會影響到所有的子圖層。這意味著你可以一次性對包含這些圖層的容器做變換,于是所有的子圖層都自動繼承了這個變換方法:
@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;
perspective.m34 = - 1.0 / 500.0;
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;
}
六、隱式動畫
事務(CATransaction)
- 事務實際上是Core Animation用來包含一系列屬性動畫集合的機制,任何用指定事務去改變可以做動畫的圖層屬性都不會立刻發生變化,而是當事務一旦提交的時候開始用一個動畫過渡到新值
主要用法:
[CATransaction begin]; // 入棧
[CATransaction commit]; //出棧
+setAnimationDuration: //設置當前事務的動畫時間
+animationDuration // 獲取值(默認0.25秒)
[CATransaction setDisableActions:YES]; //對所有屬性關閉隱式動畫
隱式動畫如何實現:
- 圖層首先檢測它是否有委托,并且是否實現CALayerDelegate協議指定的-actionForLayer:forKey方法。如果有,直接調用并返回結果。
- 如果沒有委托,或者委托沒有實現-actionForLayer:forKey方法,圖層接著檢查包含屬性名稱對應行為映射的actions字典。
- 如果actions字典沒有包含對應的屬性,那么圖層接著在它的style字典接著搜索屬性名。
- 最后,如果在style里面也找不到對應的行為,那么圖層將會直接調用定義了每個屬性的標準行為的-defaultActionForKey:方法。
所以一輪完整的搜索結束之后,-actionForKey:要么返回空(這種情況下將不會有動畫發生),要么是CAAction協議對應的對象,最后CALayer拿這個結果去對先前和當前的值做動畫。
七、顯式動畫
7.1 關鍵幀動畫(CAKeyframeAnimation)
和CABasicAnimation類似,CAKeyframeAnimation同樣是CAPropertyAnimation的一個子類,它依然作用于單一的一個屬性,但是和CABasicAnimation不一樣的是,它不限制于設置一個起始和結束的值,而是可以根據一連串隨意的值來做動畫。
- 關鍵幀起源于傳動動畫,意思是指主導的動畫在顯著改變發生時重繪當前幀(也就是關鍵幀),每幀之間剩下的繪制(可以通過關鍵幀推算出)將由熟練的藝術家來完成。CAKeyframeAnimation也是同樣的道理:你提供了顯著的幀,然后Core Animation在每幀之間進行插入。
代碼:
- (void)viewDidLoad
{
[super viewDidLoad];
//create a path
...
//create the keyframe animation
CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
// 平移動畫
animation.keyPath = @"position";
//持續時間
animation.duration = 4.0;
// 動畫路徑
animation.path = bezierPath.CGPath;
// *圖層將會根據曲線的切線自動旋轉*
animation.rotationMode = kCAAnimationRotateAuto;
[shipLayer addAnimation:animation forKey:nil];
}
八、動畫組CAAnimationGroup
- CAAnimationGroup是另一個繼承于CAAnimation的子類,它添加了一個animations數組的屬性,用來組合別的動畫。實例代碼:
- (void)viewDidLoad
{
[super viewDidLoad];
//create a path
UIBezierPath *bezierPath = [[UIBezierPath alloc] init];
[bezierPath moveToPoint:CGPointMake(0, 150)];
[bezierPath addCurveToPoint:CGPointMake(300, 150) controlPoint1:CGPointMake(75, 0) controlPoint2:CGPointMake(225, 300)];
//draw the path using a CAShapeLayer
CAShapeLayer *pathLayer = [CAShapeLayer layer];
pathLayer.path = bezierPath.CGPath;
pathLayer.fillColor = [UIColor clearColor].CGColor;
pathLayer.strokeColor = [UIColor redColor].CGColor;
pathLayer.lineWidth = 3.0f;
[self.containerView.layer addSublayer:pathLayer];
//add a colored layer
CALayer *colorLayer = [CALayer layer];
colorLayer.frame = CGRectMake(0, 0, 64, 64);
colorLayer.position = CGPointMake(0, 150);
colorLayer.backgroundColor = [UIColor greenColor].CGColor;
[self.containerView.layer addSublayer:colorLayer];
//create the position animation
CAKeyframeAnimation *animation1 = [CAKeyframeAnimation animation];
animation1.keyPath = @"position";
animation1.path = bezierPath.CGPath;
animation1.rotationMode = kCAAnimationRotateAuto;
//create the color animation
CABasicAnimation *animation2 = [CABasicAnimation animation];
animation2.keyPath = @"backgroundColor";
animation2.toValue = (__bridge id)[UIColor redColor].CGColor;
//create group animation
CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];
groupAnimation.animations = @[animation1, animation2];
groupAnimation.duration = 4.0;
//add the animation to the color layer
[colorLayer addAnimation:groupAnimation forKey:nil];
}
九、CATransition(過渡動畫)
-
type(動畫類型)
- kCATransitionFade (淡入淡出)
- kCATransitionMoveIn(從頂部滑動進入)
- kCATransitionPush
- kCATransitionReveal
-
subtype(動畫方向)
- kCATransitionFromRight
- kCATransitionFromLeft
- kCATransitionFromTop
- kCATransitionFromBottom
在動畫過程中取消動畫
- (void)removeAnimationForKey:(NSString *)key; // 移除某個動畫
- (void)removeAllAnimations; // 移除所有動畫
十、CAMediaTiming協議
10.1 概念
- CAMediaTiming協議定義了在一段動畫內用來控制逝去時間的屬性的集合,CALayer和CAAnimation都實現了這個協議,所以時間可以被任意基于一個圖層或者一段動畫的類控制。
10.2 屬性
- duration:CFTimeInterval的類型,對將要進行的動畫的一次迭代指定了時間;
- repeatCount:動畫重復的迭代次數;
- repeatDuration:動畫重復一個指定的時間,而不是指定次數;
- autoreverses:(BOOL類型)在每次間隔交替循環過程中自動回放。
10.3 注意
- duration和repeatCount默認都是0。但這不意味著動畫時長為0秒,或者0次,這里的0僅僅代表了“默認”,也就是0.25秒和1次;
- 把repeatDuration設置為INFINITY,于是動畫無限循環播放,設置repeatCount為INFINITY也有同樣的效果。
- repeatCount和repeatDuration可能會相互沖突,所以你只要對其中一個指定非零值.
10.4 相對時間
- beginTime:指定了動畫開始之前的的延遲時間。這里的延遲從動畫添加到可見圖層的那一刻開始測量,默認是0(就是說動畫會立刻執行)
- speed:是一個時間的倍數,默認1.0,減少它會減慢圖層/動畫的時間,增加它會加快速度。如果2.0的速度,那么對于一個duration為1的動畫,實際上在0.5秒的時候就已經完成了.
- timeOffset:增加timeOffset只是讓動畫快進到某一點,例如,對于一個持續1秒的動畫來說,設置timeOffset為0.5意味著動畫將從一半的地方開始.
基于定時器的動畫
NSTimer并不準確的原因:
iOS上的每個線程都管理了一個NSRunloop,通過一個循環來完成一些任務列表。當你設置一個NSTimer,他會被插入到當前任務列表中,然后直到指定時間過去之后才會被執行。但是何時啟動定時器并沒有一個時間上限,而且它只會在列表中上一個任務完成之后開始執行。這通常會導致有幾毫秒的延遲,但是如果上一個任務過了很久才完成就會導致延遲很長一段時間.
性能優化(待完善)
cell.layer.shouldRasterize = YES;
cell.layer.rasterizationScale = [UIScreen mainScreen].scale;