使用CA动画也有段时间了,相对都是比较基本的一些动画。使用的范围基本都是layer自己的animateble的属性。那对于CALayer子类扩展的属性该如何实现动画呢?
我想做一个圆形的进度条,通过CABasicAnimation动画更新progress,效果如下:
首先了解下layer自己的属性如果实现动画的。layer加载时会通过+ (BOOL)needsDisplayForKey:(NSString *)key 方法来判断当前属性改变是否需要重新绘制。如果想实现自定义动画就需要重载这个方法,当key等于扩展属性时return yes即可;
来看下 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];
}
好的,看下效果:
恩,不错,layer的效果实现了,现在再来创建CircleView。
#import <UIKit/UIKit.h>
#import "CircleLayer.h"
@interface CircleView :UIView
@property(nonatomic ,assign)CGFloat progress;
@end
+(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"];
}
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];
}
跑起来看下效果:
修改move方法:
- (void)move:(UIGestureRecognizer*)tap{
layer.position =CGPointMake(250,250);
}
没有给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;
效果图:
看到了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.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];
}
效果图:
核心代码都贴出来,有疑问的可以留言,共同探讨!