CALayer扩展属性自定义CAAnimation动画

使用CA动画也有段时间了,相对都是比较基本的一些动画。使用的范围基本都是layer自己的animateble的属性。那对于CALayer子类扩展的属性该如何实现动画呢?

我想做一个圆形的进度条,通过CABasicAnimation动画更新progress,效果如下:

CALayer扩展属性自定义CAAnimation动画_第1张图片

首先了解下layer自己的属性如果实现动画的。layer加载时会通过+ (BOOL)needsDisplayForKey:(NSString *)key 方法来判断当前属性改变是否需要重新绘制。如果想实现自定义动画就需要重载这个方法,当key等于扩展属性时return yes即可;


上一步实现扩展属性改变便会自动调用 setNeedsDisplay这样就会触发重绘,达到我们想要的效果。

来看下 setNeedsDisplay之后layer及其delegate的相关方法调用。

layer方法响应链有两种:

① [layer setNeedDisplay] -> [layer displayIfNeed] -> [layer display] -> [layerDelegate displayLayer:]

② [layer setNeedDisplay] -> [layer displayIfNeed] -> [layer display] -> [layer drawInContext:] -> [layerDelegate drawLayer: inContext:]

说明一下,如果layerDelegate实现了displayeLayer:协议,之后layer就不会再调用自身的重绘代码。

我们使用第二种方式来实现我们的圆形进度条,将代码集成到layer中,降低耦合。

我们先来创建一个CircleLayer类:

#import <QuartzCore/QuartzCore.h>

#import <CoreGraphics/CoreGraphics.h>

#import <UIKit/UIKit.h>

@interface CircleLayer :CALayer

@property(nonatomic ,assign)CGFloat progress;

@end


在.m中重载一下这两个方法:

- (void)drawInContext:(CGContextRef)ctx

{

    NSLog(@"%s",__FUNCTION__);

    

    CGContextSetLineWidth(ctx,5.f);

    CGContextSetStrokeColorWithColor(ctx, [UIColorblackColor].CGColor);

    CGContextAddArc(ctx,CGRectGetWidth(self.bounds)/2.,CGRectGetHeight(self.bounds)/2.,CGRectGetWidth(self.bounds)/2. - 6, M_PI_2 * 3,M_PI_2*3+M_PI*2*self.progress,0);

    CGContextStrokePath(ctx);

    

}



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

   NSLog(@"__%s__ %@",__FUNCTION__,key);

    return [keyisEqualToString:@"progress"]?YES:[superneedsDisplayForKey:key];

}


稍微说明下:drawInContext 绘制一个圆圈,根据progress绘制出实际的弧度。needsDisplayForKey 自然是让progress自动调用setNeedsDisplay。

 



好的,CircleLayer实现之后,在viewcontroller里边添加:

    layer  = [[CircleLayeralloc]init];

   layer.frame =CGRectMake(50,100,100, 100);

    layer.backgroundColor = [UIColorredColor].CGColor;

    [self.view.layeraddSublayer:layer];


然后给self.view添加一个单机手势,来更改progress查看动画

UITapGestureRecognizer* tap = [[UITapGestureRecognizeralloc]initWithTarget:selfaction:@selector(move:)];

    [self.viewaddGestureRecognizer:tap];

实单move : 

- (void)move:(UIGestureRecognizer*)tap{

    

    layer.progress =1.f;

    [CATransactionbegin];

    [CATransactionsetDisableActions:YES];

    CABasicAnimation* animat = [CABasicAnimationanimationWithKeyPath:@"progress"];

    animat.duration =6.f;

    animat.fromValue = [NSNumbernumberWithFloat:[(CircleLayer*)[layerpresentationLayer]progress]];

    animat.toValue = [NSNumbernumberWithFloat:1.0f];

    animat.timingFunction = [CAMediaTimingFunctionfunctionWithName:kCAMediaTimingFunctionEaseInEaseOut];

    [layeraddAnimation:animatforKey:@"gogogo"];

    [CATransactioncommit];

}


好的,看下效果:

CALayer扩展属性自定义CAAnimation动画_第2张图片

恩,不错,layer的效果实现了,现在再来创建CircleView。

#import <UIKit/UIKit.h>

#import "CircleLayer.h"

@interface CircleView :UIView

@property(nonatomic ,assign)CGFloat progress;

@end

CircleView.m中:

+(Class)layerClass{

    return [CircleLayerclass];

}


- (void)setProgress:(CGFloat)progress{

   _progress = progress;

    [self.layersetValue:@(progress)forKey:@"progress"];

    CABasicAnimation* animat = [CABasicAnimationanimationWithKeyPath:@"progress"];

    animat.duration =6.f;

    animat.fromValue = [NSNumbernumberWithFloat:[(CircleLayer*)[self.layerpresentationLayer]progress]];

    animat.toValue = [NSNumbernumberWithFloat:1.0f];

    animat.timingFunction = [CAMediaTimingFunctionfunctionWithName:kCAMediaTimingFunctionEaseInEaseOut];

    [self.layeraddAnimation:animatforKey:@"gogogo"];


}


说明下,layerClass一定要实现,这个关系到CircleView的layer是CircleLayer。

viewController中添加view:

    view = [[CircleViewalloc]init];

   view.frame =CGRectMake(200,100,100, 100);

    view.backgroundColor = [UIColorblueColor];

    [self.viewaddSubview:view];

    



修改move方法:

- (void)move:(UIGestureRecognizer*)tap{

    view.progress =1.f;

    layer.progress =1.f;

    [CATransactionbegin];

    [CATransactionsetDisableActions:YES];

    CABasicAnimation* animat = [CABasicAnimationanimationWithKeyPath:@"progress"];

    animat.duration =6.f;

    animat.fromValue = [NSNumbernumberWithFloat:[(CircleLayer*)[layerpresentationLayer]progress]];

    animat.toValue = [NSNumbernumberWithFloat:1.0f];

    animat.timingFunction = [CAMediaTimingFunctionfunctionWithName:kCAMediaTimingFunctionEaseInEaseOut];

    [layeraddAnimation:animatforKey:@"gogogo"];

    [CATransactioncommit];

}


跑起来看下效果:



CALayer扩展属性自定义CAAnimation动画_第3张图片

好了,效果基本都实现了,但是代码有点臃肿,CABasicAnimation那块代码粘过来粘过去,实在是难看,那如果实现直接setProgress直接进行动画,而不是需要显示添加呢?
看下layer本身的属性都有个隐士动画,如果我们也能添加那不是很爽?
先看下layer的隐士动画:

修改move方法:

- (void)move:(UIGestureRecognizer*)tap{

   layer.position =CGPointMake(250,250);

}


效果:

CALayer扩展属性自定义CAAnimation动画_第4张图片

没有给layer添加任何动画,但是改变中心点实际效果有动画效果时常0.25.

为了确认这一点,更改下animation的duration:


在viewDidLoad中加入以下代码(初始化layer的地方):

//修改事务默认的action,延长时间观察隐士动画效果

    CABasicAnimation* animation = [CABasicAnimationanimationWithKeyPath:@"position"];

    animation.duration =5.f;

    

    NSMutableDictionary* action = [NSMutableDictionarydictionaryWithDictionary:layer.actions];

   NSLog(@"action = %@",action);

    action[@"position"] = animation;

   layer.actions = action;


效果图:



CALayer扩展属性自定义CAAnimation动画_第5张图片


看到了action的效果,现在可以重载

- (id<CAAction>)actionForKey:(NSString *)event;

实现我们想要的效果。

    在Circlelayer中添加以下代码:

- (id<CAAction>)actionForKey:(NSString *)event{

   NSLog(@"event = %@",event);

    if (self.presentationLayer !=nil && [eventisEqualToString:@"progress"]) {

        NSLog(@"pre = %lf , lay = %lf",[[self.presentationLayervalueForKey:@"progress"]floatValue],self.progress);


        CABasicAnimation* animat = [CABasicAnimationanimationWithKeyPath:@"progress"];

        animat.duration =6.f;

        animat.fromValue = [NSNumbernumberWithFloat:[(CircleLayer*)[selfpresentationLayer]progress]];

        animat.timingFunction = [CAMediaTimingFunctionfunctionWithName:kCAMediaTimingFunctionEaseInEaseOut];

       return animat;

    }

   return [superactionForKey:event];

}


然后删除CircleView中的CABasicAnimation,viewcontroller中的CABasicAnimation


CircleView.m中

- (void)setProgress:(CGFloat)progress{

   _progress = progress;

    [self.layersetValue:@(progress)forKey:@"progress"];


}

viewcontroller.m中

- (void)viewDidLoad {

    [superviewDidLoad];

    self.view.backgroundColor = [UIColorwhiteColor];

    

     layer  = [[CircleLayeralloc]init];

   layer.frame =CGRectMake(50,100,100, 100);

    layer.backgroundColor = [UIColorredColor].CGColor;

    [self.view.layeraddSublayer:layer];

    

    

    UILabel* lb1 = [[UILabelalloc]initWithFrame:CGRectMake(CGRectGetMinX(layer.frame),CGRectGetMaxY(layer.frame) +10, CGRectGetWidth(layer.frame),30)];

    lb1.text =@"layer";

    lb1.textAlignment =NSTextAlignmentCenter;

    lb1.textColor = [UIColorblackColor];

    lb1.font = [UIFontpreferredFontForTextStyle:UIFontTextStyleBody];

    [self.viewaddSubview:lb1];

    

    view = [[CircleViewalloc]init];

   view.frame =CGRectMake(200,100,100, 100);

    view.backgroundColor = [UIColorblueColor];

    [self.viewaddSubview:view];

    

    UILabel* lb2 = [[UILabelalloc]initWithFrame:CGRectMake(CGRectGetMinX(view.frame),CGRectGetMaxY(view.frame) +10, CGRectGetWidth(view.frame),30)];

    lb2.text =@"view";

    lb2.textAlignment =NSTextAlignmentCenter;

    lb2.textColor = [UIColorblackColor];

    lb2.font = [UIFontpreferredFontForTextStyle:UIFontTextStyleBody];

    [self.viewaddSubview:lb2];

    


    UITapGestureRecognizer* tap = [[UITapGestureRecognizeralloc]initWithTarget:selfaction:@selector(move:)];

    [self.viewaddGestureRecognizer:tap];

    

}



- (void)move:(UIGestureRecognizer*)tap{


    layer.progress =1.f;

    view.progress  =1.f;

}


效果图:




到这里效果代码都有了,如果想要在CircleView中自定义Circlelayer的progress动画,可以关闭隐士动画,修改如下;

- (void)setProgress:(CGFloat)progress{

   _progress = progress;

    [CATransactionbegin];

    [CATransactionsetDisableActions:YES];

    [self.layersetValue:@(progress)forKey:@"progress"];

    [CATransactioncommit];

}



效果图:

CALayer扩展属性自定义CAAnimation动画_第6张图片

核心代码都贴出来,有疑问的可以留言,共同探讨!




你可能感兴趣的:(CAlayer,CAAnimation,自定义动画)