CALayer的隱式動畫

我們修改layer屬性時默認會有動畫。動畫使用CABasicAnimation對象,持續(xù)0.25。默認會產生隱式動畫的layer屬性:文檔連接

Core Animation使用action對象來執(zhí)行我們修改屬性產生的隱式動畫。那么什么是action對象?(action對象是文檔的原文,翻譯叫隱式動畫感覺有點奇怪,但是兩者應該是等同的)

什么是action對象?

action對象遵照CAAction協(xié)議,并且自定義了一些可能在layer上執(zhí)行的相關行為。比如CAAnimation類,修改Layer屬性會產生動畫就是通過執(zhí)行它生成的。

如何創(chuàng)建action對象?

action對象遵照CAAction協(xié)議,并執(zhí)行協(xié)議方法:[runActionForKey:object:arguments:]。你可以在這個類里進行一些自定義設置。下面代碼創(chuàng)建了一個遵照CAAction協(xié)議,并且在協(xié)議方法里執(zhí)行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對象觸發(fā)過程:

1.和action對象有關的事件被觸發(fā)
2.創(chuàng)建對應的action對象
3.執(zhí)行action對象

1.和action對象有關的事件被觸發(fā)
觸發(fā)事件包括:

  • layer的屬性被修改。包括layer的任何屬性,不僅僅只是會產生動畫的部分。
  • layer被添加到layer階層。標識符key是kCAOnOrder。
  • layer被移除layer階層。標示符key是kCAOnOrderOut。
  • layer將參與transition動畫。標示符key是kCATransition。(mac os)

2.創(chuàng)建action對象
layer調用actionForKey:方法搜索需要執(zhí)行的action對象。action對象可以根據情況在不同的方法里被,具體情況如下按順序考慮:
(因為layer搜索到一個action對象就會停止搜索。layer會根據下面順序優(yōu)先選擇靠前的方法)

1.如果設置了layer的代理,可以通過執(zhí)行代理方法actionForLayer:forKey:返回一個action對象。代理方法可以返回:

  • 返回action對象,例如CAAnimation對象。
  • 返回nil。nil表示結束actionForLayer:forKey:方法的執(zhí)行,繼續(xù)搜索下一個階段。
  • 返回[NSNull null]。表示結束搜索,即結束actionForLayer:forKey:,也結束其他階段,將不會有隱式動畫。

2.查找layer的actions屬性,看key是否有對應的值。
3.查找layer的style屬性。
4.layer調用defaultActionForKey:方法。
5.如果搜索到了最后階段,layer會執(zhí)行一個默認的action對象,一般是CABasicAnimation。

上面方法具體選擇那種我也不知道!

3.調用action對象的runActionForLayer:object:arguments:方法執(zhí)行相關操作。
如果返回的是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這種方式呢?后來我發(fā)現actionForLayer:是在layer屬性值改變前調用的,,而action對象runActionForKey:方法是在layer屬性值發(fā)生變化之后發(fā)生的,比如我設置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:會在屬性值發(fā)生變化前調用,而runActionForKey:會在屬性值發(fā)生變化后調用,需要注意。

討論

關于actionForKey :actionForLayer:兩個方法,我不是很理解兩個的區(qū)別,因為這兩個方法都是返回action對象,而且actionForLayer:需要設置代理。因此我在actionForKey :里返回對應的action對象不是更好嗎?
當然還有一個區(qū)別:如果在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:方法將不會被調用,隱式動畫也不會生成。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容