iOS中为CALayer子类的自定义属性添加动画
Core Animation 隐式的为很多图层属性添加动画,比如:position,transform,contents,通过改变其属性值,便可以达到动画效果.但如果我们要自定义CALayer.那么CALayer子类的自定义属性如何也能在创建子类后,修改属性值就可以达到动画效果呢?
先来看一下动画效果:
![](https://img.haomeiwen.com/i2374945/ace607e601efd912.gif)
我们为自定义图层添加一个新属性radius,在默认情况下,radius是没有动画的,因此,改变半径会导致圆形逐渐消失并出现新的圆形.这不是我们想要的结果,我们希望在动画执行过程中,radius的动画效果与positon是同步执行的.
那么,让我们一步步实现吧!
- 既然是自定义图层,那么我们先创建一个类继承自CALayer,命名为CircleLayer.并且为CircleLayer添加属性radius.
@interface CircleLayer : CALayer
// 自定义属性,radius半径
@property (nonatomic,assign) CGFloat radius;
2,重写初始化方法,并且在里面调用setNeedsDisPlay:方法,这样图层的drawInContext:会在第一次添加图层的时候就会被调用.
- (instancetype)init{
self = [super init];
if (self) {
[self setNeedsDisplay];
}
return self;
}
3,实现drawInContext:方法.在图层初始化后就会显示在这个方法里面绘制的图形.这里我们绘制的是一个圆,属性radius便是圆的半径,关于图形绘制的问题不在本次讨论之列,这里我就不在赘述了.
- (void)drawInContext:(CGContextRef)ctx{
CGContextSetFillColorWithColor(ctx, [UIColor redColor].CGColor);
CGFloat radius = self.radius;
CGRect rect;
rect.size = CGSizeMake(radius * 2, radius * 2);
rect.origin.x = (self.bounds.size.width - radius * 2) / 2;
rect.origin.y = (self.bounds.size.height - radius * 2) / 2;
CGContextAddEllipseInRect(ctx, rect);
CGContextFillPath(ctx);
}
4,覆盖needDisplayForKey:方法,这是最为主要的方法,在这个方法我们判断如果里面的key值与radius相同的话,便返回yes.这样我们就可以检测radius这个属性值的变化,如果发现属性值变了,就可以自动重绘.
+(BOOL)needsDisplayForKey:(NSString *)key{
if ([key isEqualToString:@"radius"]){
return YES;
}
return [super needsDisplayForKey:key];
}
5,现在要修改动作了.我们需要实现actionForKey:方法,以此返回一个在当前图层(presentationLayer)中有半径起点值的动画.这就可以让动画发生过程中,动画效果更加的平滑.
-(id<CAAction>)actionForKey:(NSString *)event{
if ([self presentationLayer] != nil) {
if ([event isEqualToString:@"radius"]) {
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"radius"];
anim.fromValue = [[self presentationLayer] valueForKey:@"radius"];
return anim;
}
}
return [super actionForKey:event];
}
6,好了,基本上自定义图层的实现基本上就结束了,让我们看看如何在控制器中使用吧.在这里我用了一个动画组来展现图层的显示.
- (void)viewDidLoad {
[super viewDidLoad];
// 初始化CircleLayer对象.
CircleLayer *circleLayer = [CircleLayer Layer];
circleLayer.radius =30;
shapeLayer.backgroundColor = [UIColor blueColor].CGColor;
circleLayer.frame = self.view.bounds;
self.circleLayer = circleLayer;
[self.view.layer addSublayer:circleLayer];
// 给图层的position属性添加动画.
CABasicAnimation *anim = [CABasicAnimation
animationWithKeyPath:@"position"];
anim.duration = 2;
NSMutableDictionary *actions = [NSMutableDictionary
dictionaryWithDictionary:
[circleLayer actions]];
actions[@"position"] = anim;
CABasicAnimation *fadeAnim = [CABasicAnimation
animationWithKeyPath:@"opacity"];
fadeAnim.fromValue = @0.4;
fadeAnim.toValue = @1.0;
CABasicAnimation *growAnim = [CABasicAnimation
animationWithKeyPath:
@"transform.scale"];
growAnim.fromValue = @0.8;
growAnim.toValue = @1.0;
// 这里我们使用一个动画组,将使用baseAnimation的动画fadeAnim与growAnim添加到动画组中.用动画形式将图层展示出来.
CAAnimationGroup *groupAnim = [CAAnimationGroup animation];
groupAnim.animations = @[fadeAnim, growAnim];
groupAnim.duration = 1.5;
actions[kCAOnOrderIn] = groupAnim;
circleLayer.actions = actions;
//给View一个手势,点击后实现自定义属性的动画.
UIGestureRecognizer *g = [[UITapGestureRecognizer alloc]
initWithTarget:self
action:@selector(tap:)];
[self.view addGestureRecognizer:g];
}
- (void)tap:(UIGestureRecognizer *)recognizer {
self.circleLayer.position = CGPointMake(100, 100);
[CATransaction setAnimationDuration:2];
// 修改radius的属性值.
self.circleLayer.radius = 100.0;
}
需要注意的点:CALayer在运行时对这个radius自动生成了set和get方法,并且这些存取方法有重要的逻辑,关键是不要在CALayer中实现自定义的存取方法或是使用@synthesize.但是在运行过程中,会报没有实现radius属性set方法的错误.所以在这里我们使用@dynamic实现radius属性.@dynamic就是告诉编译器,不自动生成setter和getter方法,这样就可以让编译器通过编译了.
希望能对你们能有所帮助和启发.谢谢!