CALayer动画

CALayer动画

简介

CALayer用来管理图像内容并且允许你在上面执行动画

CABasicAnimation通过改变CALayer的属性来实现一个基础的动画

基本用法

1
2
3
4
5
6
7
8
9
10
11
CALayer* layer = [[CALayer alloc] init];

CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];

animation.duration = 1.0;

animation.byValue = @(M_PI * 2);

animation.repeatCount = -1;

[layer addAnimation:animation forKey:nil];

以上是基本用法,CABasicAnimation通过修改CALayer的旋转变换实现一个动画

进阶用法

通常我们会通过重写CALayer的drawInContext的方法来实现自绘的内容,比如绘制一个圆之类或者更复杂的图像,这时候我们的直接使用动画可能会无法满足我们的需求,比如我们只想旋转一个图元而不是CALayer这个整体,由于CABasicAnimation是通过改变CALayer的属性来实现的动画的,我们可以通过添加自定义属性来对整体的内容进行更近准的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@interface CLayer : CALayer

@property (nonatomic, assign) CGFloat r;

@end

@implementation CLayer
@synthesize r;

- (void)drawInContext:(CGContextRef)ctx {
float ratio = self.frame.size.width * 0.5;
CGContextSetLineWidth(ctx, 1/ratio);
CGContextSetRGBFillColor(ctx, 1, 0, 0, 1);
CGContextTranslateCTM(ctx, self.frame.size.width*0.5, self.frame.size.height*0.5);
CGContextScaleCTM(ctx, ratio, -ratio);
CGContextSetRGBFillColor(ctx, 1, 1, 0, 1);
CGContextRotateCTM(ctx, self.r);
CGContextFillEllipseInRect(ctx, CGRectMake(0, 0, 1, 1));
}

- (instancetype)initWithLayer:(id)layer {
if ((self = [super initWithLayer:layer])) {
if ([layer isKindOfClass:[CLayer class]]) {
// Copy custom property values between layers
CLayer *other = (CLayer *)layer;
self.r = other.r;
}
}
return self;
}

- (void)setR:(CGFloat)val {
r = val;
[self setNeedsDisplay];
}

+ (BOOL) needsDisplayForKey:(NSString *) key {
if ([key isEqualToString:@"r"]) {
return YES;
}
return [super needsDisplayForKey:key];
}


@end

以上的代码重写了CALayer的绘制函数并且自定义了一个CALayer的属性,之后我们可以通过CABasicAnimation来产生一个属性动画,从而完成某些特定的效果,下面会对关键的代码进行说明


1
2
3
4
5
6
7
8
@property (nonatomic, assign) CGFloat r;

...

- (void)setR:(CGFloat)val {
r = val;
[self setNeedsDisplay];
}

理所当然的我们需要一个自定义属性来让CABasicAnimation来更改


1
- (void)drawInContext:(CGContextRef)ctx { ... }

自定义绘制代码,里面的坐标变换仅仅是为了绘制方便而已


1
2
3
4
5
6
7
+ (BOOL) needsDisplayForKey:(NSString *) key {
if ([key isEqualToString:@"r"]) {
return YES;
}
return [super needsDisplayForKey:key];
}

必须要重写该方法,否则会认为这个属性不会对显示内容造成影响,作用于该属性的动画不会生效


1
2
3
4
5
6
7
8
9
- (instancetype)initWithLayer:(id)layer {
if ((self = [super initWithLayer:layer])) {
if ([layer isKindOfClass:[CLayer class]]) {
CLayer *other = (CLayer *)layer;
self.r = other.r;
}
}
return self;
}

事实上删除掉这个方法在这个例子里完全不会影响最终的结果,但是如果你需要多个自定义的属性的话就必须重写该方法并且需要保证属性正确的赋值,因为CABasicAnimation在执行的动画会调用initWithLayer产生新的CALayer,默认的方法不会处理自定义属性,所以需要我们自己进行来处理


之后我们可以就可以操作自定义属性来实现动画

1
2
3
4
5
6
7
8
CLayer* layer = [[CLayer alloc] init];
CABasicAnimation* anim = [CABasicAnimation animationWithKeyPath:@"r"];
anim.fromValue = @(0);
anim.toValue = @(1);
anim.repeatCount = INFINITY;
anim.fillMode = kCAFillModeForwards;
anim.duration = 3;
[layer addAnimation:anim forKey:@""];