知识点汇总(五)

基于CALayer 的动画实现

需求:

想要动态的画出一个水波纹。

思路:

基于CALayer画出基本的样式,结合动画实现动态水波纹的效果。

问题:

CABasicAnimation 的基本动画只有以下几组:

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
animationWithKeyPath的值:

transform.scale = 比例轉換
transform.scale.x = 闊的比例轉換
transform.scale.y = 高的比例轉換
transform.rotation.z = 平面圖的旋轉
opacity = 透明度

margin
zPosition

backgroundColor

cornerRadius
borderWidth


bounds
contents

contentsRect
cornerRadius
frame

hidden

mask

masksToBounds
opacity

position

shadowColor

shadowOffset

shadowOpacity
shadowRadius

特别注意,frame的设置动画是无效的,它的变动影响了bounds以及center的变化。

那么,我想要给一个自定义的属性添加动画怎么处理?

查看文档,发现一个方法:

1
2
3
4
5
6
7
8
9
 /* Method for subclasses to override. Returning true for a given
* property causes the layer's contents to be redrawn when the property
* is changed (including when changed by an animation attached to the
* layer). The default implementation returns NO. Subclasses should
* call super for properties defined by the superclass. (For example,
* do not try to return YES for properties implemented by CALayer,
* doing will have undefined results.) */

+ (BOOL)needsDisplayForKey:(NSString *)key;

这个方法可以预处理CALayer的属性,若不想识别这个属性,可返回NO,否则返回YES,其余默认的属性,可按照父类的返回返回。

具体实现

.h 的实现

1
2
3
4
5
6
7
8
 
#import <QuartzCore/QuartzCore.h>

@interface Coslayer : CALayer

@property (nonatomic) CGFloat offsetAngle;

@end

.m内的具体实现

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
46
47
48
49
50
#import "Coslayer.h"

#import <UIKit/UIKit.h>
@implementation Coslayer

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

- (instancetype)init{
self = [super init];
self.backgroundColor = [UIColor yellowColor].CGColor;
self.offsetAngle = 0;
return self;
}

- (void)setOffsetAngle:(CGFloat)offsetAngle{
_offsetAngle = offsetAngle;
[self setNeedsDisplay];
}

- (void)drawInContext:(CGContextRef)ctx{
int widthPixel = (int)CGRectGetWidth(self.bounds);
if (widthPixel <1) {
return;
}
//180 作为一个波纹
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, 0, 0);
CGPathMoveToPoint(path, NULL, 0, CGRectGetMidY(self.bounds));
for (int i = 0; i < widthPixel; i++) {
CGFloat y = 50 + 8*cos(2*(i*M_PI/360.0+self.offsetAngle));
if (i == 0) {
CGPathMoveToPoint(path, NULL, i, y);
}else{
CGPathAddLineToPoint(path, NULL, i, y);
}
}
CGPathAddLineToPoint(path, NULL, CGRectGetWidth(self.bounds), 0);
CGPathAddLineToPoint(path, NULL, 0, 0);
CGContextAddPath(ctx, path);
CGContextSetRGBFillColor(ctx, 0.8, 0.5, 0.7, 1);
CGContextDrawPath(ctx, kCGPathFill);
CGPathRelease(path);
}

@end

动画调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  
Coslayer *layer = [Coslayer layer];
layer.frame = CGRectMake(100, 100, 400, 400);
[self.view.layer addSublayer:layer];

CABasicAnimation *basic = [CABasicAnimation animationWithKeyPath:@"offsetAngle"];
basic.fromValue = 0;
basic.toValue = [NSNumber numberWithFloat:M_PI*2];
basic.byValue = [NSNumber numberWithFloat:M_PI];
basic.duration = 0.6;
basic.autoreverses = NO;
basic.speed = 0.4;
basic.repeatCount = HUGE_VALF;
[layer addAnimation:basic forKey:@"offsetAngle"];

CABasicAnimation 初始化实例对象时要求传入keyPath,这个就是对应的路径。

上述例子就是指offsetAngle从0到2π的过渡,过渡值为π,过渡时间为0.6,不自动转回。

repeatCount在api中已经说明,是动画的次数,若想设置为无限次的重复动画值为HUGE_VALF。