Core Animation是一個復(fù)合引擎妹沙,它的職責(zé)就是盡可能快地組合屏幕上不同的可視內(nèi)容膘壶,這個內(nèi)容是被分解成獨立的圖層,存儲在一個叫做圖層樹的體系之中脓斩。
1. UIView與CALayer的關(guān)系:
CALayer在概念上和UIView類似,同樣也是一些被層級關(guān)系樹管理的矩形塊畴栖,同樣也可以包含一些內(nèi)容(像圖片,文本或者背景色)八千,管理子圖層的位置吗讶。并且它們有一些方法和屬性用來做動畫和變換,但是CALayer不能響應(yīng)事件恋捆。
UIView是基于CALayer的一種封裝照皆,每一個UIView都有一個CALayer實例的圖層屬性,也就是所謂的backing layer沸停,視圖的職責(zé)就是創(chuàng)建并管理這個圖層膜毁,以確保當(dāng)子視圖在層級關(guān)系中添加或者被移除的時候,他們關(guān)聯(lián)的圖層也同樣對應(yīng)在層級關(guān)系樹當(dāng)中有相同的操作愤钾。UIView的尺寸樣式以及內(nèi)容都由CALayer提供瘟滨。
大多數(shù)情況下我們都使用UIView而不使用CALayer的原因是,首先是CALayer不能響應(yīng)事件能颁,其次是CALayer不方便自動布局杂瘸,最重要的是蘋果已經(jīng)基于CALayer封裝好了UIView,我們在使用UIView高級接口的同時也能使用CALayer的底層功能伙菊。
既然這里提到了層級樹败玉,順便提一下事件響應(yīng)鏈:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
UIResponder * next = [self nextResponder];
NSMutableString * prefix = @"-".mutableCopy;
while (next != nil) {
NSLog(@"%@%@", prefix, [next class]);
[prefix appendString: @"--"];
next = [next nextResponder];
}
}
2. CALayer內(nèi)容相關(guān)的屬性
-
contents
可以給CALayer的contents賦值
self.layerView.layer.contents = (__bridge id)image.CGImage;
-
contentGravity
可以類似設(shè)置UIView的contentModel一樣敌土,設(shè)置CALayer的contentGravity
self.layerView.layer.contentsGravity = kCAGravityResizeAspect;
contentGravity可以賦值的常量值如下
CA_EXTERN NSString * const kCAGravityCenter
CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCAGravityTop
CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCAGravityBottom
CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCAGravityLeft
CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCAGravityRight
CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCAGravityTopLeft
CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCAGravityTopRight
CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCAGravityBottomLeft
CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCAGravityBottomRight
CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCAGravityResize
CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCAGravityResizeAspect
CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCAGravityResizeAspectFill
CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
-
contentsScale
CALayer的contentsScale屬性可以用以適配retina屏幕,該屬性值默認(rèn)為1.0运翼,將會以每個點1個像素繪制圖片返干,如果設(shè)置為2.0,則會以每個點2個像素繪制圖片血淌,這就是我們熟知的Retina屏幕矩欠。
UIImage *image = [UIImage imageNamed:@"doll"];
UIView *view = [[UIView alloc] initWithFrame:self.view.bounds];
view.layer.contents = (__bridge id _Nullable)(image.CGImage);
view.layer.contentsGravity = kCAGravityCenter;
[self.view addSubview:view];
運行結(jié)果:
可以看到顯示出來的圖片有像素顆粒感,在上面代碼的基礎(chǔ)上再加上一句
view.layer.contentsScale = [UIScreen mainScreen].scale;
運行結(jié)果:
-
maskToBounds
類似UIView的clipsToBounds六剥,CALayer有一個屬性叫maskToBounds
self.layerView.layer.masksToBounds = YES;
-
contentsRect
CALayer的contentsRect屬性可以用于圖片拼合
舉個栗子晚顷,有如下圖片:
我們可以利用contentsRect屬性來分別單獨顯示三個娃娃:
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *leftTopImage;
@property (weak, nonatomic) IBOutlet UIView *rightTopImage;
@property (weak, nonatomic) IBOutlet UIView *leftBottomImage;
@end
@implementation ViewController
-(void)viewDidLoad {
[super viewDidLoad];
UIImage *image = [UIImage imageNamed:@"doll"];
self.leftTopImage.layer.contents = (__bridge id _Nullable)(image.CGImage);
self.leftTopImage.layer.contentsRect = CGRectMake(0.0f, 0.0f, 0.5f, 0.5f);
self.rightTopImage.layer.contents = (__bridge id _Nullable)(image.CGImage);
self.rightTopImage.layer.contentsRect = CGRectMake(0.5f, 0.0f, 0.5f, 0.5f);
self.leftBottomImage.layer.contents = (__bridge id _Nullable)(image.CGImage);
self.leftBottomImage.layer.contentsRect = CGRectMake(0.0f, 0.5f, 0.5f, 0.5f);
}
@end
運行結(jié)果:
拼合不僅給app提供了一個整潔的載入方式,還有效地提高了載入性能(單張大圖比多張小圖載入地更快)疗疟,但是如果有手動安排的話该默,他們還是有一些不方便的,如果你需要在一個已經(jīng)創(chuàng)建好的品和圖上做一些尺寸上的修改或者其他變動策彤,無疑是比較麻煩的栓袖。
Mac上有一些商業(yè)軟件可以為你自動拼合圖片,這些工具自動生成一個包含拼合后的坐標(biāo)的XML或者plist文件店诗,拼合圖片的使用大大簡化裹刮。這個文件可以和圖片一同載入,并給每個拼合的圖層設(shè)置contentsRect庞瘸,這樣開發(fā)者就不用手動寫代碼來擺放位置了捧弃。
-
contentsCenter
CALayer的另一個屬性contentsCenter,有點類似于UIImage里的-resizableImageWithCapInsets: 方法擦囊,樓主淺薄违霞,實在不懂如何描述,這里有篇文章講得很清楚:iOS: 關(guān)于CALayer的contentsCenter屬性
3. CALayer位置坐標(biāo)相關(guān)的屬性
對應(yīng)于UIView的frame瞬场、bounds买鸽、center,CALayer有frame贯被、bounds眼五、position,訪問UIView的這三個屬性彤灶,返回的其實就是CALayer對應(yīng)的這三個值看幼。但是CALayer另外還有一個比較難于理解的屬性anchorPoint。
-
anchorPoint
anchorPoint決定了CALayer的那個點處于position的位置幌陕,其作用效果如下圖:
-
坐標(biāo)轉(zhuǎn)換
CALayer也有用于在不同坐標(biāo)系中轉(zhuǎn)換坐標(biāo)的函數(shù):
-(CGPoint)convertPoint:(CGPoint)p fromLayer:(nullable CALayer *)l;
-(CGPoint)convertPoint:(CGPoint)p toLayer:(nullable CALayer *)l;
-(CGRect)convertRect:(CGRect)r fromLayer:(nullable CALayer *)l;
-(CGRect)convertRect:(CGRect)r toLayer:(nullable CALayer *)l;
-
zPosition
修改zPosition:
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *blueView;
@property (weak, nonatomic) IBOutlet UIView *orangeView;
@end
@implementation ViewController
-(void)viewDidLoad {
[super viewDidLoad];
self.blueView.layer.zPosition = 1.0f;
}
@end```
運行結(jié)果:

zPosition屬性雖然可以改變圖層的顯示順序,但是不能改變圖層在圖層樹中的順序
- #####CALayer事件處理
CALayer并不關(guān)心任何響應(yīng)鏈?zhǔn)录危圆荒苤苯犹幚碛|摸事件或者手勢茅诱。但是它有一系列的方法幫你處理事件:-containsPoint:和-hitTest:逗物。
-containsPoint:接受一個在本圖層坐標(biāo)系下的CGPoint,如果這個點在圖層frame范圍內(nèi)就返回YES瑟俭。舉個栗子:
import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *whiteView;
@property (weak, nonatomic) IBOutlet UIView *orangeView;
@end
@implementation ViewController
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
UITouch *touch = touches.anyObject;
CGPoint originPoint = [touch locationInView:self.view];
CGPoint thePoint = [self.whiteView.layer convertPoint:originPoint fromLayer:self.view.layer];
BOOL containsResult = [self.whiteView.layer containsPoint:thePoint];
if (containsResult) {
thePoint = [self.orangeView.layer convertPoint:thePoint fromLayer:self.whiteView.layer];
containsResult = [self.orangeView.layer containsPoint:thePoint];
UIAlertController *alert;
if (containsResult) {
alert = [UIAlertController alertControllerWithTitle:nil message:@"inside orange layer" preferredStyle:UIAlertControllerStyleAlert];
}else{
alert = [UIAlertController alertControllerWithTitle:nil message:@"inside white layer" preferredStyle:UIAlertControllerStyleAlert];
}
[alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
}
}
@end
運行結(jié)果:

-hitTest:方法同樣接受一個CGPoint類型參數(shù)翎卓,而不是BOOL類型,它返回圖層本身摆寄,或者包含這個坐標(biāo)點的葉子節(jié)點圖層失暴,如果這個點在最外面圖層的范圍之外,則返回nil微饥。
下面這段代碼實現(xiàn)了上圖一樣的效果:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
UITouch *touch = touches.anyObject;
CGPoint originPoint = [touch locationInView:self.view];
CALayer *layer = [self.whiteView.layer hitTest:originPoint];
UIAlertController *alert;
if (layer == self.orangeView.layer) {
alert = [UIAlertController alertControllerWithTitle:nil message:@"inside orange layer" preferredStyle:UIAlertControllerStyleAlert];
}else{
alert = [UIAlertController alertControllerWithTitle:nil message:@"inside white layer" preferredStyle:UIAlertControllerStyleAlert];
}
[alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
}
---
#4. CALayer視覺相關(guān)的屬性
- #####conrnerRadius
CALayer有一個叫做conrnerRadius的屬性控制著圖層角的曲率逗扒。它是一個浮點數(shù),默認(rèn)為0(為0的時候就是直角)欠橘,但是你可以把它設(shè)置成任意值矩肩。默認(rèn)情況下,這個曲率值只影響背景顏色而不影響背景圖片或是子圖層肃续。不過黍檩,如果把masksToBounds設(shè)置成YES的話,圖層里面的所有東西都會被截取始锚。
Interface Build布局如下:

添加如下代碼:
import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *topWhiteView;
@property (weak, nonatomic) IBOutlet UIView *bottomWhiteView;
@end
@implementation ViewController
-(void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.topWhiteView.layer.cornerRadius = 20.0f;
self.bottomWhiteView.layer.cornerRadius = 20.0f;
self.bottomWhiteView.layer.masksToBounds = YES;
}
@end
運行效果:

- #####borderWidth和borderColor
CALayer另外兩個非常有用屬性就是borderWidth和borderColor刽酱。二者共同定義了圖層邊的繪制樣式。這條線(也被稱作stroke)沿著圖層的bounds繪制瞧捌,同時也包含圖層的角棵里。在上面代碼的基礎(chǔ)上再加上幾句:
self.topWhiteView.layer.borderWidth = 5.0f;
self.bottomWhiteView.layer.borderWidth = 5.0f;
self.topWhiteView.layer.borderColor = [UIColor yellowColor].CGColor;
self.bottomWhiteView.layer.borderColor = [UIColor yellowColor].CGColor;
運行效果:

- #####陰影效果
CALayer還有一系列屬性用來繪制陰影:
/** Shadow properties. */
/ The color of the shadow. Defaults to opaque black. Colors created
- from patterns are currently NOT supported. Animatable. /
@property(nullable) CGColorRef shadowColor;
/ The opacity of the shadow. Defaults to 0. Specifying a value outside the - [0,1] range will give undefined results. Animatable. /
@property float shadowOpacity;
/ The shadow offset. Defaults to (0, -3). Animatable. /
@property CGSize shadowOffset;
/ The blur radius used to create the shadow. Defaults to 3. Animatable. /
@property CGFloat shadowRadius;
/ When non-null this path defines the outline used to construct the - layer's shadow instead of using the layer's composited alpha
- channel. The path is rendered using the non-zero winding rule.
- Specifying the path explicitly using this property will usually
- improve rendering performance, as will sharing the same path
- reference across multiple layers. Upon assignment the path is copied.
- Defaults to null. Animatable. */
@property(nullable) CGPathRef shadowPath;
在前面代碼的基礎(chǔ)上再加上幾句:
self.topWhiteView.layer.shadowColor = [UIColor blackColor].CGColor;
self.bottomWhiteView.layer.shadowColor = [UIColor blackColor].CGColor;
self.topWhiteView.layer.shadowOpacity = 0.5f;
self.bottomWhiteView.layer.shadowOpacity = 0.5f;
運行效果:

發(fā)現(xiàn)兩個問題:
1、topWhiteView有陰影姐呐,但是陰影在視圖的上方
2殿怜、而bottomWhiteView干脆根本沒有陰影
第一個問題的原因在于這個屬性:
/* The shadow offset. Defaults to (0, -3). Animatable. */
@property CGSize shadowOffset;
CALayer的陰影偏移量默認(rèn)為(0, -3),所以陰影出現(xiàn)在了視圖的上方
第二個問題的原因在于這句代碼:
self.bottomWhiteView.layer.masksToBounds = YES;
如果你開啟了masksToBounds屬性皮钠,所有從圖層中突出來的內(nèi)容都會被才剪掉,一個簡單的解決辦法就是在bottomWhiteView的下面再插入一個view來充當(dāng)陰影視圖赠法。
修改之前的代碼麦轰,最終代碼如下:
import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *topWhiteView;
@property (weak, nonatomic) IBOutlet UIView *bottomWhiteView;
@property (weak, nonatomic) IBOutlet UIView *bottomBackView;
@end
@implementation ViewController
-(void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.bottomWhiteView.layer.masksToBounds = YES;
[self configLayer:self.topWhiteView.layer];
[self configLayer:self.bottomWhiteView.layer];
[self configLayer:self.bottomBackView.layer];
}
-(void)configLayer:(CALayer *)aLayer{
aLayer.cornerRadius = 20.0f; //圓角
aLayer.borderWidth = 5.0f; //邊框線寬
aLayer.borderColor = [UIColor yellowColor].CGColor; //邊框顏色
aLayer.shadowColor = [UIColor blackColor].CGColor; //陰影顏色
aLayer.shadowOpacity = 0.5f; //陰影透明度
aLayer.shadowOffset = CGSizeMake(5.0f, 5.0f); //陰影偏移量
}
@end
運行效果:

- #####shadowRadius
shadowRadius屬性可以控制CALayer陰影的模糊度,在viewDidLoad方法的最后加上一句:
self.topWhiteView.layer.shadowRadius = 10.0f;
運行結(jié)果:

- #####shadowPath
shadowPath可以為CALayer指定特定的陰影路徑砖织。
舉個栗子:
import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *orangeView;
@property (weak, nonatomic) IBOutlet UIView *cyanView;
@end
@implementation ViewController
-(void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self configLayer:self.orangeView.layer];
[self configLayer:self.cyanView.layer];
//create a square shadow
CGMutablePathRef squarePath = CGPathCreateMutable();
CGPathAddRect(squarePath, NULL, self.cyanView.bounds);
self.cyanView.layer.shadowPath = squarePath; CGPathRelease(squarePath);
//create a circular shadow
CGMutablePathRef circlePath = CGPathCreateMutable();
CGPathAddEllipseInRect(circlePath, NULL, self.orangeView.bounds);
self.orangeView.layer.shadowPath = circlePath; CGPathRelease(circlePath);
}
-(void)configLayer:(CALayer *)aLayer{
aLayer.shadowOpacity = 0.5f;
aLayer.cornerRadius = aLayer.bounds.size.width / 2.0f;
aLayer.shadowOffset = CGSizeMake(0.0f, 0.0f);
aLayer.shadowRadius = 5.0f;
}
@end
運行效果:

- #####mask
mask圖層定義了父圖層的部分可見區(qū)域款侵,mask圖層的Color屬性是無關(guān)緊要的,真正重要的是圖層的輪廓侧纯。mask屬性就像是一個餅干切割機(jī)新锈,mask圖層實心的部分會被保留下來,其他的則會被拋棄眶熬。
舉個栗子:
Interface Build布局如下:

使用下面這個小雪人來充當(dāng)圖層蒙版:

代碼如下:
import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
@implementation ViewController
-(void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//create mask layer
CALayer *maskLayer = [CALayer layer];
maskLayer.frame = self.imageView.bounds;
UIImage *maskImage = [UIImage imageNamed:@"3"];
maskLayer.contents = (__bridge id)maskImage.CGImage;
maskLayer.contentsGravity = kCAGravityCenter;
maskLayer.contentsScale = [UIScreen mainScreen].scale;
//apply mask to image layer
self.imageView.layer.mask = maskLayer;
}
@end
運行效果:

一個利用圖層蒙版實現(xiàn)的有趣動畫:[iOS-maskLayer實現(xiàn)的炫酷動畫](http://www.reibang.com/p/a52ba15edc35)
[Responder Chain](http://blog.csdn.net/chun799/article/details/8223612)妹笆、[Core Animation](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreAnimation_guide/Introduction/Introduction.html)块请、[UIView與CALayer的關(guān)系](http://www.cocoachina.com/ios/20150828/13244.html)、[[iOS: 關(guān)于CALayer的contentsCenter屬性](https://www.mgenware.com/blog/?p=489)](https://www.mgenware.com/blog/?p=489)拳缠、[iOS-maskLayer實現(xiàn)的炫酷動畫](http://www.reibang.com/p/a52ba15edc35)