iOS动画iOS点点滴滴

iOS中为CALayer子类的自定义属性添加动画

2016-07-01  本文已影响658人  吴与伦

Core Animation 隐式的为很多图层属性添加动画,比如:position,transform,contents,通过改变其属性值,便可以达到动画效果.但如果我们要自定义CALayer.那么CALayer子类的自定义属性如何也能在创建子类后,修改属性值就可以达到动画效果呢?

先来看一下动画效果:


我们为自定义图层添加一个新属性radius,在默认情况下,radius是没有动画的,因此,改变半径会导致圆形逐渐消失并出现新的圆形.这不是我们想要的结果,我们希望在动画执行过程中,radius的动画效果与positon是同步执行的.

那么,让我们一步步实现吧!

  1. 既然是自定义图层,那么我们先创建一个类继承自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方法,这样就可以让编译器通过编译了.

希望能对你们能有所帮助和启发.谢谢!

上一篇 下一篇

猜你喜欢

热点阅读