我们修改layer属性时默认会有动画。动画使用CABasicAnimation对象,持续0.25。默认会产生隐式动画的layer属性:文档连接
Core Animation使用action对象
来执行我们修改属性产生的隐式动画。那么什么是action对象
?(action对象
是文档的原文,翻译叫隐式动画感觉有点奇怪,但是两者应该是等同的)
什么是action对象?
action对象
遵照CAAction
协议,并且自定义了一些可能在layer上执行的相关行为。比如CAAnimation类,修改Layer属性会产生动画就是通过执行它生成的。
如何创建action对象?
action对象
遵照CAAction
协议,并执行协议方法:[runActionForKey:object:arguments:]
。你可以在这个类里进行一些自定义设置。下面代码创建了一个遵照CAAction
协议,并且在协议方法里执行CABasicAnimation
对象:
@interface CustomAction : NSObject
@property (nonatomic) CGColorRef currentColor;
@end
@implementation CustomAction
- (void)runActionForKey:(NSString *)key object:(id)anObject arguments:(NSDictionary *)dict {
CustomLayer *layer = anObject;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"backgroundColor"];
animation.fromValue = (id)[UIColor greenColor].CGColor;
animation.toValue = (id)[UIColor redColor].CGColor;
animation.duration = 5;
[layer addAnimation:animation forKey:@"backgroundColor"];
}
@end
action对象触发过程:
1.和action对象
有关的事件被触发
2.创建对应的action对象
3.执行action对象
1.和action对象有关的事件被触发
触发事件包括:
- layer的属性被修改。包括layer的任何属性,不仅仅只是会产生动画的部分。
- layer被添加到layer阶层。标识符key是kCAOnOrder。
- layer被移除layer阶层。标示符key是kCAOnOrderOut。
- layer将参与transition动画。标示符key是kCATransition。(mac os)
2.创建action对象
layer调用actionForKey:
方法搜索需要执行的action对象
。action对象
可以根据情况在不同的方法里被,具体情况如下按顺序考虑:
(因为layer搜索到一个action对象就会停止搜索。layer会根据下面顺序优先选择靠前的方法)
1.如果设置了layer的代理,可以通过执行代理方法actionForLayer:forKey:
返回一个action对象
。代理方法可以返回:
- 返回
action对象
,例如CAAnimation
对象。 - 返回
nil
。nil
表示结束actionForLayer:forKey:
方法的执行,继续搜索下一个阶段。 - 返回
[NSNull null]
。表示结束搜索,即结束actionForLayer:forKey:
,也结束其他阶段,将不会有隐式动画。
2.查找layer的actions
属性,看key是否有对应的值。
3.查找layer的style
属性。
4.layer调用defaultActionForKey:
方法。
5.如果搜索到了最后阶段,layer会执行一个默认的action对象
,一般是CABasicAnimation
。
上面方法具体选择那种我也不知道!
3.调用action对象的runActionForLayer:object:arguments:
方法执行相关操作。
如果返回的是CAAnimation实例,那么可以不实现runActionForLayer:object:arguments:
方法,因为Core Animaiton已经替你做好了CAAnimation的实现。
下面两种方式是一样的,一般我们都是使用CAAnimation,那么我们直接在actionForLayer
里实现我们想要的动画效果就行了,也就是代码2:
代码1:
- (id)actionForLayer:(CALayer *)layer forKey:(NSString *)event{
if ([event isEqualToString:@"backgroundColor"]) {
MyAction *action = [MyAction new];
return action;
}
return nil;
}
@interface MyAction : NSObject
@end
@implementation MyAction
- (void)runActionForKey:(NSString *)event object:(id)anObject arguments:(NSDictionary *)dict{
CustomLayer *layer = anObject;
CABasicAnimation *animation = [CABasicAnimation animation];
animation.duration = 3.0f;
[layer addAnimation:animation forKey:@"backgroundColor"];
}
@end
代码2:
- (id)actionForLayer:(CALayer *)layer forKey:(NSString *)event{
if ([event isEqualToString:@"backgroundColor"]) {
CABasicAnimation *animation = [CABasicAnimation animation];
animation.duration = 3.0f;
[layer addAnimation:animation forKey:@"backgroundColor"];
return animation;
}
return nil;
}
那么为什么还有代码1这种方式呢?后来我发现actionForLayer:
是在layer属性值改变前调用的,,而action对象
的runActionForKey:
方法是在layer属性值发生变化之后发生的,比如我设置CABasicAnimation的fromValue
和toValue
的值,就需要在action对象
里实现:
@interface CircularProgressAction : NSObject
@property (assign , nonatomic) float oldValue;
@end
@implementation CircularProgressAction
- (void)runActionForKey:(NSString *)event object:(id)anObject arguments:(NSDictionary *)dict{
CircularProgress *layer = anObject;
CABasicAnimation * animation=[CABasicAnimation animationWithKeyPath:@"strokeEnd"];
animation.duration=3;
animation.fromValue=[NSNumber numberWithFloat:self.oldValue/100.0];
animation.toValue=[NSNumber numberWithFloat:[[layer valueForKey:event] floatValue]/100.0];
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
[layer addAnimation:animation forKey:@"strokeEnd"];
}
@end
通过改变CALayer的自定义属性来产生自定义的默认动画
代码示例:
下面代码通过改变CircularProgress
类中的arcLenght
的值来产生动画:
动画效果:
***************CircularProgress.h****************
#import
@interface CircularProgress : CAShapeLayer
//1~100
@property (assign, nonatomic) float arcLenght;
- (instancetype)initWithFrame:(CGRect)frame;
@end
@interface CircularProgressAction : NSObject
@property (assign , nonatomic) float oldValue;
@end
************** CircularProgress.m******************
#import "CircularProgress.h"
#import
@interface CircularProgress()
@end
@implementation CircularProgress
@dynamic arcLenght;
- (instancetype)initWithFrame:(CGRect)frame{
if (self = [super init]) {
[self setupLayers:frame];
}
return self;
}
- (void)setupLayers:(CGRect)frame{
UIBezierPath *path = [UIBezierPath bezierPath];
[path addArcWithCenter:CGPointMake(frame.size.width/2, frame.size.height/2) radius:50 startAngle:0 endAngle:2*M_PI clockwise:NO];
self.path = path.CGPath;
self.fillColor = [UIColor clearColor].CGColor;
self.strokeColor = [UIColor greenColor].CGColor;
self.lineWidth = 3;
self.delegate = self;
self.strokeStart = 0;
self.strokeEnd = 0;
}
- (id)actionForLayer:(CALayer *)layer forKey:(NSString *)event{
CircularProgressAction *action = nil;
if ([event isEqualToString:@"arcLenght"]) {
action = [[CircularProgressAction alloc] init];
action.oldValue = self.arcLenght;
}
return action;
}
- (id)actionForKey:(NSString *)event{
return [super actionForKey:event];
}
@end
@implementation CircularProgressAction
- (void)runActionForKey:(NSString *)event object:(id)anObject arguments:(NSDictionary *)dict{
CircularProgress *layer = anObject;
CABasicAnimation * animation=[CABasicAnimation animationWithKeyPath:@"strokeEnd"];
animation.duration=3;
animation.fromValue=[NSNumber numberWithFloat:self.oldValue/100.0];
animation.toValue=[NSNumber numberWithFloat:[[layer valueForKey:event] floatValue]/100.0];
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
[layer addAnimation:animation forKey:@"strokeEnd"];
}
@end
*****************ViewController.m*****************
NSArray * colors = @[(id)[[self colorWithHex:0xFF6347] CGColor],
(id)[[self colorWithHex:0xFFEC8B] CGColor],
(id)[[self colorWithHex:0x98FB98] CGColor],
(id)[[self colorWithHex:0x00B2EE] CGColor],
(id)[[self colorWithHex:0x9400D3] CGColor]];
NSArray * locations = @[@0.1,@0.3,@0.5,@0.7,@1];
CAGradientLayer *gradientLayer = [CAGradientLayer new];
gradientLayer.frame = CGRectMake(100, 100, 200, 200);
gradientLayer.colors = colors;
gradientLayer.locations = locations;
gradientLayer.startPoint = CGPointMake(0, 0);
gradientLayer.endPoint = CGPointMake(1, 0);
[self.view.layer addSublayer:gradientLayer];
self.circularProgress = [[CircularProgress alloc] initWithFrame:gradientLayer.bounds];
gradientLayer.mask = self.circularProgress;
- (void)doRightButtonAction{
self.circularProgress.arcLenght = 50;
}
注意:
-
arcLenght
的值一定要有变化才能引起actionForKey :
进行搜索。 -
arcLenght
需要用关键字@dynamic
修饰。 -
actionForKey :
和actionForLayer:
会在属性值发生变化前调用,而runActionForKey:
会在属性值发生变化后调用,需要注意。
讨论
关于actionForKey :
和actionForLayer:
两个方法,我不是很理解两个的区别,因为这两个方法都是返回action对象
,而且actionForLayer:
需要设置代理。因此我在actionForKey :
里返回对应的action对象
不是更好吗?
当然还有一个区别:如果在actionForKey :
里返回nil
或[NSNull null]
,那么搜素就会停止,而如果在actionForLayer:
里返回nil
会停止actionForLayer:
去搜素下一阶段,返回[NSNull null]
才会停止搜索。
取消隐式动画
你可以是用CATransaction
类临时取消隐式动画
[CATransaction begin];
[CATransaction setDisableActions:YES];
NSInteger x = arc4random() % 100;
self.circularProgress.arcLenght = x;
[CATransaction commit];
设置setDisableActions:
为YES后,layer的actionForKey:
方法将不会被调用,隐式动画也不会生成。