美文网首页iOS动画iOS点点滴滴
iOS中为CALayer子类的自定义属性添加动画

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

作者: 吴与伦 | 来源:发表于2016-07-01 15:05 被阅读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方法,这样就可以让编译器通过编译了.

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

    相关文章

      网友评论

      本文标题:iOS中为CALayer子类的自定义属性添加动画

      本文链接:https://www.haomeiwen.com/subject/igbfjttx.html