CoreAnimation之CALayer基礎
CoreAnimation之變換
1. CAShapeLayer
CAShapeLayer是一個通過矢量圖形而不是bitmap來繪制的圖層子類,使用CAShapeLayer有以下一些優(yōu)點:
渲染快速——CAShapeLayer使用了硬件加速银觅,繪制同一圖形會比用Core Graphics快很多
高效使用內存——一個CAShapeLayer不需要像普通CALayer一樣創(chuàng)建一個寄宿圖形荔烧,所以無論有多大违寞,都不會占用太多的內存
不會被圖層邊界剪裁掉——一個CAShapeLayer可以在邊界之外繪制。你的圖層路徑不會像在使用Core Graphics的普通CALayer一樣被剪裁掉
不會出現像素化——當你給CAShapeLayer做3D變換時,它不像一個有寄宿圖的普通圖層一樣變得像素化
CAShapeLayer可以用來繪制所有能夠通過CGPath來表示的形狀店归。這個形狀不一定要閉合,圖層路徑也不一定要不可破酪我,事實上你可以在一個圖層上繪制好幾個不同的形狀消痛。你可以控制一些屬性比如lineWith,lineCap都哭,和lineJoin秩伞。但是在圖層層面你只有一次機會設置這些屬性,如果你想用不同顏色或風格來繪制多個形狀欺矫,就不得不為每個形狀準備一個圖層了纱新。
說了這么多,CAShapeLayer到底能用來做什么呢穆趴?
-
CAShapeLayer實現視圖的部分圓角:
-(void)drawCorner{
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 100.0f, 100.0f)];
view.center = self.view.center;
view.backgroundColor = [UIColor blackColor];
[self.view addSubview:view];
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:view.frame byRoundingCorners:UIRectCornerTopLeft | UIRectCornerBottomRight cornerRadii:CGSizeMake(30.0f, 30.0f)];
CAShapeLayer *layer = [[CAShapeLayer alloc] init];
layer.bounds = view.frame;
layer.position = CGPointMake(50.0f, 50.0f);
layer.path = path.CGPath;
view.layer.mask = layer;
}
運行效果:
-
CAShapeLayer實現一個呆萌的火柴人:
-(void)drawMatchman{
CGFloat radius = 25.0f; //半徑
UIBezierPath *path = [[UIBezierPath alloc] init];
CGPoint point1 = CGPointMake(self.view.center.x + radius, self.view.center.y);
[path moveToPoint:point1]; //將畫筆移動到point1
[path addArcWithCenter:self.view.center radius:radius startAngle:0.0f endAngle:2.0f*M_PI clockwise:YES]; //畫一個圓代表火柴人的頭
CGPoint point2 = CGPointMake(point1.x - radius, point1.y + radius);
[path moveToPoint:point2]; //將畫筆移動到point2脸爱,準備畫身體
CGPoint point3 = CGPointMake(point2.x, point2.y+50.0f);
[path addLineToPoint:point3]; //畫一根長為50的豎線代表火柴人的身體,起點是point2毡代,終點是point3
CGPoint point4 = CGPointMake(point3.x - radius, point3.y+25.0f);
[path addLineToPoint:point4]; //畫一根長為25的左斜線代表火柴人的左腳阅羹,起點是point3,終點是point4
[path moveToPoint:point3]; //將畫筆移動到point3教寂,準備畫右腳
CGPoint point5 = CGPointMake(point3.x + radius, point3.y+25.0f);
[path addLineToPoint:point5]; //畫一根長為25的右斜線代表火柴人的右腳捏鱼,起點是point3,終點是point5
//最后畫一根橫線酪耕,代表火柴人的手
CGPoint point6 = CGPointMake(point2.x - radius, point2.y + 25.0f);
[path moveToPoint:point6];
[path addLineToPoint:CGPointMake(point6.x + 50.0f, point6.y)];
shapeLayer = [CAShapeLayer layer];
shapeLayer.strokeColor = [UIColor redColor].CGColor; //畫筆顏色
shapeLayer.fillColor = [UIColor clearColor].CGColor; //填充色
shapeLayer.lineWidth = 6.0f; //線條寬度
shapeLayer.lineJoin = kCALineJoinRound; //線條連接處的樣式
shapeLayer.lineCap = kCALineCapRound; //線條末端處的樣式
shapeLayer.path = path.CGPath;
[self.view.layer addSublayer:shapeLayer];
[self addFlagPoint:point1];
[self addFlagPoint:point2];
[self addFlagPoint:point3];
[self addFlagPoint:point4];
[self addFlagPoint:point5];
[self addFlagPoint:point6];
}
-(void)addFlagPoint:(CGPoint)aPoint{
UIView *flag = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 6.0f, 6.0f)];
flag.center = aPoint;
flag.layer.cornerRadius = 3.0f;
flag.backgroundColor = [UIColor blackColor];
[self.view addSubview:flag];
}
運行效果:
-
CAShapeLayer實現一個扇形動畫:
-(void)drawCircular{
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 100.0f, 100.0f)];
view.center = self.view.center;
UIImage *image = [UIImage imageNamed:@"3"];
view.layer.contents = (__bridge id _Nullable)(image.CGImage);
view.layer.contentsGravity = kCAGravityCenter;
view.layer.contentsScale = [UIScreen mainScreen].scale;
[self.view addSubview:view];
shapeLayer = [CAShapeLayer layer];
shapeLayer.frame = view.bounds;
shapeLayer.strokeEnd = 0.0f;
shapeLayer.strokeStart = 0.0f;
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:view.bounds];
shapeLayer.path = path.CGPath;
shapeLayer.fillColor = [UIColor clearColor].CGColor;
shapeLayer.lineWidth = 100.0f;
shapeLayer.strokeColor = [UIColor redColor].CGColor;
view.layer.mask = shapeLayer;
[NSTimer scheduledTimerWithTimeInterval:0.005f target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
}
-(void)timerAction{
static BOOL flag = NO;
if (shapeLayer.strokeEnd >= 1.5f) {
flag = YES;
}
if (shapeLayer.strokeEnd <= -0.5f) {
flag = NO;
}
if (flag) {
shapeLayer.strokeEnd -= 0.005f;
}else{
shapeLayer.strokeEnd += 0.005f;
}
}
運行效果:
更多關于CAShapeLayer的動畫:動畫黃金搭檔:CADisplayLink & CAShapeLayer
2. CATransformLayer
之前我們在CoreAnimation之變換中構造了一個殘缺的正方體导梆,最后在旋轉正方體的時候遇到了問題,原因在于CALayer是扁平的,所以直接將superLayer繞y軸旋轉的時候看不出正方體的3D效果
CoreAnimation有一個專用圖層叫CATransformLayer看尼,它是CALayer的子類递鹉,但是不同于普通的CALayer,因為它不能顯示它自己的內容藏斩,只有當存在了一個能作用域子圖層的變換它才真正存在躏结,而且CATransformLayer并不平面化它的子圖層,所以它能夠用于構造一個層級的3D結構
總之一句話狰域,CATransformLayer相當于一個容器媳拴,一個3D的容器
這次我們依然以構建一個正方體為例,開始寫代碼前兆览,我們不妨在腦袋里先構造一下這個正方體:
首先屈溉,有六塊木板,都是平放在一個3D空間里
第一步抬探,構建上下兩面子巾,把上面這塊木板往上移動50個點(即沿z軸移動50個點);再把下面這塊木板往下移動50個點(即沿z軸移動-50個點)小压,這樣就構建出了上下兩面
第二步线梗,構建左右兩面,將左面這塊木板往左邊直立起來(即沿y軸旋轉-90度)场航,再將左面這塊木板往上移動50個點(即沿z軸移動50個點)缠导;同理,右面這塊木板就是先往右邊直立起來再往上移動
第三步溉痢,構建前后兩面,將前面這塊木板往前邊直立起來(即沿x軸旋轉-90度)憋他,再將前面這塊木板往上移動50個點(即沿z軸移動50個點)孩饼;同理,后面這塊木板就是先往后邊直立起來再往上移動
代碼如下(樓主也是初學竹挡,為了邏輯清晰镀娶,就沒有封裝方法,直接一個面一個方法揪罕,所以代碼有點多哈):
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController{
CATransformLayer *transformLayer;
NSTimer *timer;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
CATransform3D sublayerTransform = CATransform3DIdentity;
sublayerTransform.m34 = -1.0f/500.0f;
self.view.layer.sublayerTransform = sublayerTransform;
transformLayer = [CATransformLayer layer];
transformLayer.position = self.view.layer.position;
CATransform3D transform = CATransform3DIdentity;
transform = CATransform3DRotate(transform, -M_PI_4, 1.0f, 0.0f, 0.0f);
transform = CATransform3DRotate(transform, -M_PI_4, 0.0f, 1.0f, 0.0f);
transformLayer.transform = transform;
[self.view.layer addSublayer:transformLayer];
[self addFace1];
[self addFace2];
[self addFace3];
[self addFace4];
[self addFace5];
[self addFace6];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[timer invalidate];
timer = nil;
timer = [NSTimer scheduledTimerWithTimeInterval:0.025f target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
}
-(void)timerAction{
CATransform3D transform = transformLayer.transform;
transform = CATransform3DRotate(transform, 1.0f/180.0f*M_PI, 1.0f, 1.0f, 0.0f);
transformLayer.transform = transform;
}
//上面 ———— 把上面這塊木板往上移動50個點(即沿z軸移動50個點)
-(void)addFace1{
CALayer *layer = [[CALayer alloc] init];
layer.bounds = CGRectMake(0.0f, 0.0f, 100.0f, 100.0f);
layer.backgroundColor = [UIColor redColor].CGColor;
CATransform3D transform = CATransform3DIdentity;
transform = CATransform3DTranslate(transform, 0.0f, 0.0f, 50.0f);
layer.transform = transform;
[transformLayer addSublayer:layer];
}
//下面 ———— 把下面這塊木板往下移動50個點(即沿z軸移動-50個點)
-(void)addFace2{
CALayer *layer = [[CALayer alloc] init];
layer.bounds = CGRectMake(0.0f, 0.0f, 100.0f, 100.0f);
layer.backgroundColor = [UIColor orangeColor].CGColor;
CATransform3D transform = CATransform3DIdentity;
transform = CATransform3DTranslate(transform, 0.0f, 0.0f, -50.0f);
layer.transform = transform;
[transformLayer addSublayer:layer];
}
//左面
-(void)addFace3{
CALayer *layer = [[CALayer alloc] init];
layer.bounds = CGRectMake(0.0f, 0.0f, 100.0f, 100.0f);
layer.backgroundColor = [UIColor yellowColor].CGColor;
CATransform3D transform = CATransform3DIdentity;
//將左面這塊木板往左邊直立起來(即沿y軸旋轉-90度)
transform = CATransform3DRotate(transform, -M_PI_2, 0.0f, 1.0f, 0.0f);
//再將左面這塊木板往上移動50個點(即沿z軸移動50個點)
transform = CATransform3DTranslate(transform, 0.0f, 0.0f, 50.0f);
layer.transform = transform;
[transformLayer addSublayer:layer];
}
//右面
-(void)addFace4{
CALayer *layer = [[CALayer alloc] init];
layer.bounds = CGRectMake(0.0f, 0.0f, 100.0f, 100.0f);
layer.backgroundColor = [UIColor greenColor].CGColor;
CATransform3D transform = CATransform3DIdentity;
//將右面這塊木板往右邊直立起來(即沿y軸旋轉90度)
transform = CATransform3DRotate(transform, M_PI_2, 0.0f, 1.0f, 0.0f);
//再將右面這塊木板往上移動50個點(即沿z軸移動50個點)
transform = CATransform3DTranslate(transform, 0.0f, 0.0f, 50.0f);
layer.transform = transform;
[transformLayer addSublayer:layer];
}
//前面
-(void)addFace5{
CALayer *layer = [[CALayer alloc] init];
layer.bounds = CGRectMake(0.0f, 0.0f, 100.0f, 100.0f);
layer.backgroundColor = [UIColor blueColor].CGColor;
CATransform3D transform = CATransform3DIdentity;
//將前面這塊木板往前邊直立起來(即沿x軸旋轉-90度)
transform = CATransform3DRotate(transform, -M_PI_2, 1.0f, 0.0f, 0.0f);
//再將前面這塊木板往上移動50個點(即沿z軸移動50個點)
transform = CATransform3DTranslate(transform, 0.0f, 0.0f, 50.0f);
layer.transform = transform;
[transformLayer addSublayer:layer];
}
//后面
-(void)addFace6{
CALayer *layer = [[CALayer alloc] init];
layer.bounds = CGRectMake(0.0f, 0.0f, 100.0f, 100.0f);
layer.backgroundColor = [UIColor purpleColor].CGColor;
CATransform3D transform = CATransform3DIdentity;
//將后面這塊木板往后邊直立起來(即沿x軸旋轉90度)
transform = CATransform3DRotate(transform, M_PI_2, 1.0f, 0.0f, 0.0f);
//再將后面這塊木板往上移動50個點(即沿z軸移動50個點)
transform = CATransform3DTranslate(transform, 0.0f, 0.0f, 50.0f);
layer.transform = transform;
[transformLayer addSublayer:layer];
}
@end
以前在CALayer上旋轉正方體的時候我們要這樣寫(即把所有的子圖層挨個兒做一次變換):
-(void)timerAction{
static CGFloat angle = 1.0f;
CATransform3D transform3d = self.containerView.layer.sublayerTransform;
transform3d = CATransform3DRotate(transform3d, angle/180.0f*M_PI, 0.0f, 1.0f, 0.0f);
self.containerView.layer.sublayerTransform = transform3d;
}
而現在在CATransformLayer上旋轉正方體梯码,只需要將CATransformLayer繞x軸或者繞y軸旋轉就行了:
-(void)timerAction{
CATransform3D transform = transformLayer.transform;
transform = CATransform3DRotate(transform, 1.0f/180.0f*M_PI, 1.0f, 1.0f, 0.0f);
transformLayer.transform = transform;
}
運行效果:
3. CAGradientLayer
CAGradientLayer可以用來實現漸變效果:
CAGradientLayer *layer = [CAGradientLayer layer];
layer.bounds = CGRectMake(0.0f, 0.0f, 150.0f, 150.0f);
layer.position = self.view.layer.position;
layer.colors = @[(__bridge id)[UIColor redColor].CGColor,(__bridge id)[UIColor greenColor].CGColor,(__bridge id)[UIColor blueColor].CGColor];
layer.locations = @[@.25,@0.5,@0.75];
layer.startPoint = CGPointMake(0.0f, 0.0f);
layer.endPoint = CGPointMake(1.0f, 0.0f);
[self.view.layer addSublayer:layer];
運行效果:
需要特別說明一下locations這個屬性,locations數組里面裝的是相對位置好啰,這個相對位置必須是單調遞增的轩娶,但是這個位置并不是代表顏色的位置,而是說從這個位置開始框往,將要開始漸變成下一個顏色了
拿示例代碼來說鳄抒,從0.25開始,將要由紅變綠了,從0.5開始將要由綠變藍了许溅,從0.75開始又要開始下一個漸變了瓤鼻,但是由于沒有下一個顏色了,所以后面全是藍色贤重,你可以在示例代碼的colors里面再添加一個顏色試試
4. CAReplicatorLayer
學習CAReplicatorLayer之前茬祷,我們再來復習一下變換的順序:
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController{
CALayer *layer;
}
- (void)viewDidLoad {
[super viewDidLoad];
layer = [CALayer layer];
layer.backgroundColor = [UIColor cyanColor].CGColor;
UIImage *image = [UIImage imageNamed:@"3"];
layer.frame = CGRectMake(110.0f, 100.0f, 100.0f, 100.0f);
layer.contents = (__bridge id)image.CGImage;
layer.contentsGravity = kCAGravityResizeAspect;
layer.contentsScale = [UIScreen mainScreen].scale;
[self.view.layer addSublayer:layer];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self rotate];
}
-(void)rotate{
__weak __typeof__(self) weakSelf = self;
[UIView animateWithDuration:3.0f animations:^{
CATransform3D transform = layer.transform;
transform = CATransform3DRotate(transform, M_PI, 0.0f, 0.0f, 1.0f);
layer.transform = transform;
} completion:^(BOOL finished) {
[weakSelf translate];
}];
}
-(void)translate{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[UIView animateWithDuration:3.0f animations:^{
CATransform3D transform = layer.transform;
transform = CATransform3DTranslate(transform, 0.0f, 100.0f, 0.0f);
layer.transform = transform;
}];
});
}
@end
運行效果
注意這句代碼:
transform = CATransform3DTranslate(transform, 0.0f, 100.0f, 0.0f);
明明是沿y軸移動200個點,為啥運行的時候會往上跑呢?
原因在于并蝗,在移動之前祭犯,我們已經繞z軸旋轉過了:
transform = CATransform3DRotate(transform, M_PI, 0.0f, 0.0f, 1.0f);
旋轉之后的layer,其相對于superLayer的坐標系已經發(fā)生了改變
拿示例代碼來說借卧,繞z軸旋轉180度之后盹憎,x軸和y軸的方向都已經變成與原來相反的方向了
變換的順序,在CAReplicatorLayer中體現得尤為明顯铐刘,因為CAReplicatorLayer的instance的變換是逐步增加的陪每,每個實例都是相對于前一實例布局
CAReplicatorLayer實現一個帶倒影的ImageView:
#import "ReplicatorImageView.h"
@implementation ReplicatorImageView
+ (Class)layerClass{
return [CAReplicatorLayer class];
}
- (void)setUp{
CAReplicatorLayer *layer = (CAReplicatorLayer *)self.layer;
layer.instanceCount = 2;
CATransform3D transform = CATransform3DIdentity;
transform = CATransform3DScale(transform, 1, -1, 0);
transform = CATransform3DTranslate(transform, 0.0f, -self.frame.size.height, 0.0f);
layer.instanceTransform = transform;
layer.instanceAlphaOffset = -0.6;
CALayer *imageLayer = [CALayer layer];
imageLayer.frame = layer.bounds;
imageLayer.contents = (__bridge id _Nullable)(self.image.CGImage);
imageLayer.contentsScale = [UIScreen mainScreen].scale;
imageLayer.contentsGravity = kCAGravityResizeAspect;
[layer addSublayer:imageLayer];
}
-(void)setReplicatorImage:(UIImage *)replicatorImage{
self.image = replicatorImage;
[self setUp];
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
ReplicatorImageView *imageView = [[ReplicatorImageView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 150.0f, 150.0f)];
imageView.center = self.view.center;
[self.view addSubview:imageView];
UIImage *image = [UIImage imageNamed:@"3"];
imageView.replicatorImage = image;
}
運行效果:
instanceCount指定了總共要復制多少個圖層(包含本身)
instanceAlphaOffset = -0.6f; 即當前圖層實例的alpha值 = 上一個圖層實例的alpha - 0.6f,與之類似的屬性還有這些:
/* The color components added to the color of instance k-1 to produce
* the modulation color of instance k. Defaults to the clear color (no
* change). Animatable. */
@property float instanceRedOffset;
@property float instanceGreenOffset;
@property float instanceBlueOffset;
更多CAReplicatorLayer動畫:基于CAReplicatorLayer的炫酷動畫