CALayer的隐式动画

我们修改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<CAAction>
@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对象。
  • 返回nilnil表示结束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<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event{
    if ([event isEqualToString:@"backgroundColor"]) {
        MyAction *action = [MyAction new];
        return action;
    }
    return nil;
}

@interface MyAction : NSObject<CAAction>
@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<CAAction>)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的fromValuetoValue的值,就需要在action对象里实现:

@interface CircularProgressAction : NSObject<CAAction>
@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 <QuartzCore/QuartzCore.h>
@interface CircularProgress : CAShapeLayer
//1~100
@property (assign, nonatomic) float arcLenght;
- (instancetype)initWithFrame:(CGRect)frame;
@end

@interface CircularProgressAction : NSObject<CAAction>
@property (assign , nonatomic) float oldValue;
@end

************** CircularProgress.m******************
#import "CircularProgress.h"
#import <UIKit/UIKit.h>
@interface CircularProgress()<CALayerDelegate>

@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<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event{
    CircularProgressAction *action = nil;
    if ([event isEqualToString:@"arcLenght"]) {
        action = [[CircularProgressAction alloc] init];
        action.oldValue = self.arcLenght;
    }
    return action;
}

- (id<CAAction>)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;
}

注意:

  1. arcLenght的值一定要有变化才能引起actionForKey :进行搜索。
  2. arcLenght需要用关键字@dynamic修饰。
  3. 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:方法将不会被调用,隐式动画也不会生成。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容