CoreAnimation之隐式动画

CoreAnimation之CALayer基础
CoreAnimation之变换
CoreAnimation之常用图层

修改非RootLayer的一些属性(支持隐式动画的属性)会产生隐式动画

隐式动画的持续时间取决于当前事务,隐式动画的类型取决于图层行为


事务:

事务实际上是Core Animation用来包含一系列属性动画集合的机制,任何用指定事务去改变可以做动画的图层属性都不会立刻发生变化,而是当事务一旦提交的时候开始用一个动画过渡到新值

所谓事务,具体到代码中就是CATransaction类,CATransaction没有属性或者实例方法,并且也不能用+alloc和-init方法创建它,常用方法有这些:

/* Begin a new transaction for the current thread; nests. */

+ (void)begin;

/* Commit all changes made during the current transaction. Raises an
 * exception if no current transaction exists. */

+ (void)commit;

/* Commits any extant implicit transaction. Will delay the actual commit
 * until any nested explicit transactions have completed. */

+ (void)flush;

/* Methods to lock and unlock the global lock. Layer methods automatically
 * obtain this while modifying shared state, but callers may need to lock
 * around multiple operations to ensure consistency. The lock is a
 * recursive spin-lock (i.e shouldn't be held for extended periods). */

+ (void)lock;
+ (void)unlock;

/* Accessors for the "animationDuration" per-thread transaction
 * property. Defines the default duration of animations added to
 * layers. Defaults to 1/4s. */

+ (CFTimeInterval)animationDuration;
+ (void)setAnimationDuration:(CFTimeInterval)dur;

/* Accessors for the "animationTimingFunction" per-thread transaction
 * property. The default value is nil, when set to a non-nil value any
 * animations added to layers will have this value set as their
 * "timingFunction" property. Added in Mac OS X 10.6. */

+ (nullable CAMediaTimingFunction *)animationTimingFunction;
+ (void)setAnimationTimingFunction:(nullable CAMediaTimingFunction *)function;

/* Accessors for the "disableActions" per-thread transaction property.
 * Defines whether or not the layer's -actionForKey: method is used to
 * find an action (aka. implicit animation) for each layer property
 * change. Defaults to NO, i.e. implicit animations enabled. */

+ (BOOL)disableActions;
+ (void)setDisableActions:(BOOL)flag;

/* Accessors for the "completionBlock" per-thread transaction property.
 * Once set to a non-nil value the block is guaranteed to be called (on
 * the main thread) as soon as all animations subsequently added by
 * this transaction group have completed (or been removed). If no
 * animations are added before the current transaction group is
 * committed (or the completion block is set to a different value), the
 * block will be invoked immediately. Added in Mac OS X 10.6. */

#if __BLOCKS__
+ (nullable void (^)(void))completionBlock;
+ (void)setCompletionBlock:(nullable void (^)(void))block;
#endif

/* Associate arbitrary keyed-data with the current transaction (i.e.
 * with the current thread).
 *
 * Nested transactions have nested data scope, i.e. reading a key
 * searches for the innermost scope that has set it, setting a key
 * always sets it in the innermost scope.
 *
 * Currently supported transaction properties include:
 * "animationDuration", "animationTimingFunction", "completionBlock",
 * "disableActions". See method declarations above for descriptions of
 * each property.
 *
 * Attempting to set a property to a type other than its document type
 * has an undefined result. */

+ (nullable id)valueForKey:(NSString *)key;
+ (void)setValue:(nullable id)anObject forKey:(NSString *)key;

事务有显式事务和隐式事务之分

Core Animation在每个run loop周期中自动开始一次新的事务,即使你不显式的用[CATransaction begin]开始一次事务,任何在一次run loop循环中图层属性的改变都会被集中起来,然后做一次0.25秒的动画,即隐式事务

调用[CATransaction begin]开始一次事务,即为显式事务

举个栗子,新开一个事务,修改layer的backgroundColor:

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    [super touchesBegan:touches withEvent:event];
    
    [CATransaction begin];
    //修改动画的持续时间
    [CATransaction setAnimationDuration:5.0f];
    [CATransaction setCompletionBlock:^{
        NSLog(@"动画完成");
    }];
    layer.backgroundColor = [UIColor blueColor].CGColor;
    [CATransaction commit];
    
}

CoreAnimation之隐式动画_第1张图片
running.gif

UIView中也有类似的动画机制:

+ (void)beginAnimations:(nullable NSString *)animationID context:(nullable void *)context;  // additional context info passed to will start/did stop selectors. begin/commit can be nested
+ (void)commitAnimations;                                                 // starts up any animations when the top level animation is commited

iOS 4中引入了block:

+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations NS_AVAILABLE_IOS(4_0); // delay = 0.0, options = 0, completion = NULL

其实两者都在做同样的事,block形式的写法更为简洁

CATransaction的+begin和+commit方法在+animateWithDuration:animations:内部自动调用,这样block中所有属性的改变都会被事务所包含,也可以避免开发者由于对+begin和+commit匹配的失误造成的风险


行为:

为毛我修改layer的backgroundColor,隐式动画的效果一直是渐变呢,为毛不能是淡入淡出或其他效果呢?这就要看图层的行为了

图层行为,实质上是如下几步:

  • 图层首先检测它是否有委托,并且是否实现CALayerDelegate协议指定的-actionForLayer:forKey:方法,如果有,直接调用并返回结果
  • 如果没有委托,或者委托没有实现-actionForLayer:forKey:方法,图层接着检查包含属性名称对应行为映射的actions字典
  • 如果actions字典没有包含对应的属性,那么图层接着在它的style字典接着搜索属性名
  • 最后,如果在style里面也找不到对应的行为,那么图层将会直接调用定义了每个属性的标准行为的-defaultActionForKey:方法

我们一步一步试一下:

  1. 图层首先检测它是否有委托,并且是否实现CALayerDelegate协议指定的-actionForLayer:forKey:方法,如果有,直接调用并返回结果
-(void)viewDidLoad{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    self.view.backgroundColor = [UIColor groupTableViewBackgroundColor];
    layer = [CALayer layer];
    layer.backgroundColor = [UIColor redColor].CGColor;
    layer.bounds = CGRectMake(0.0f, 0.0f, 100.0f, 100.0f);
    layer.position = self.view.layer.position;
    layer.delegate = self;
    [self.view.layer addSublayer:layer];
}
-(id)actionForLayer:(CALayer *)layer forKey:(NSString *)event{
    NSLog(@"event is : %@",event);
    CATransition *transition = [CATransition animation];
    transition.type = kCATransitionPush;
    transition.subtype = kCATransitionFromLeft;
    return transition;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    [super touchesBegan:touches withEvent:event];
    [CATransaction begin];
    [CATransaction setAnimationDuration:5.0f];
    [CATransaction setCompletionBlock:^{
        NSLog(@"completion");
    }];
    layer.backgroundColor = [UIColor blueColor].CGColor;
    [CATransaction commit];
}

-actionForLayer:forKey:需要返回一个遵守协议的对象,这个对象定义了图层的行为
CATransition遵守协议,可以用于创建过渡动画,并返回给图层
CATransition是另一个CAAnimation的子类,和别的子类不同,CATransition有一个type和subtype来标识变换效果,type属性是一个NSString类型,可以被设置成如下类型:

/* Common transition types. */
CA_EXTERN NSString * const kCATransitionFade
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCATransitionMoveIn
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCATransitionPush
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCATransitionReveal
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);

subtype用来控制动画方向,提供了如下四种类型:

/* Common transition subtypes. */
CA_EXTERN NSString * const kCATransitionFromRight
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCATransitionFromLeft
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCATransitionFromTop
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCATransitionFromBottom
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CoreAnimation之隐式动画_第2张图片
running.gif
  1. 如果没有委托,或者委托没有实现-actionForLayer:forKey:方法,图层接着检查包含属性名称对应行为映射的actions字典
-(void)viewDidLoad{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    self.view.backgroundColor = [UIColor groupTableViewBackgroundColor];
    layer = [CALayer layer];
    layer.backgroundColor = [UIColor redColor].CGColor;
    layer.bounds = CGRectMake(0.0f, 0.0f, 100.0f, 100.0f);
    layer.position = self.view.layer.position;
    layer.delegate = self;
    CATransition *transition = [CATransition animation];
    transition.type = kCATransitionPush;
    transition.subtype = kCATransitionFromRight;
    layer.actions = @{@"backgroundColor":transition};
    [self.view.layer addSublayer:layer];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    [super touchesBegan:touches withEvent:event];
    [CATransaction begin];
    [CATransaction setAnimationDuration:5.0f];
    [CATransaction setCompletionBlock:^{
        NSLog(@"completion");
    }];
    layer.backgroundColor = [UIColor blueColor].CGColor;
    [CATransaction commit];
}
CoreAnimation之隐式动画_第3张图片
running.gif
  1. 如果actions字典没有包含对应的属性,那么图层接着在它的style字典接着搜索属性名
layer.style = @{@"backgroundColor":transition};

当我给layer的style赋值的时候,却并没有效果,不知道为什么了,愿知道的大神留言教我


呈现图层

当你改变一个图层的属性,属性值的确是立刻更新的(如果你读取它的数据,你会发现它的值在你设置它的那一刻就已经生效了),但是屏幕上并没有马上发生改变。这是因为你设置的属性并没有直接调整图层的外观,相反,他只是定义了图层动画结束之后将要变化的外观

如果你想读取正在动画中的图层的当前属性值,得用这个

/* Returns a copy of the layer containing all properties as they were
 * at the start of the current transaction, with any active animations
 * applied. This gives a close approximation to the version of the layer
 * that is currently displayed. Returns nil if the layer has not yet
 * been committed.
 *
 * The effect of attempting to modify the returned layer in any way is
 * undefined.
 *
 * The `sublayers', `mask' and `superlayer' properties of the returned
 * layer return the presentation versions of these properties. This
 * carries through to read-only layer methods. E.g., calling -hitTest:
 * on the result of the -presentationLayer will query the presentation
 * values of the layer tree. */

- (nullable instancetype)presentationLayer;

举个栗子:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor groupTableViewBackgroundColor];
    layer = [CALayer layer];
    layer.backgroundColor = [UIColor redColor].CGColor;
    layer.bounds = CGRectMake(0.0f, 0.0f, 100.0f, 100.0f);
    layer.position = self.view.layer.position;
    layer.delegate = self;
    
    [self.view.layer addSublayer:layer];
}

-(void)timerAction{
    NSLog(@"presentationLayer.position = %@\tlayer.position = %@",NSStringFromCGPoint(layer.presentationLayer.position),NSStringFromCGPoint(layer.position));
}

//-(id)actionForLayer:(CALayer *)layer forKey:(NSString *)event{
//    NSLog(@"event is : %@",event);
//    CATransition *transition = [CATransition animation];
//    transition.type = kCATransitionPush;
//    transition.subtype = kCATransitionFromLeft;
//    return transition;
//}

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    [super touchesBegan:touches withEvent:event];
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:.5f target:self selector:@selector(timerAction) userInfo:nil repeats:YES];

    [CATransaction begin];
    [CATransaction setAnimationDuration:5.0f];
    [CATransaction setCompletionBlock:^{
        NSLog(@"completion");
        [timer invalidate];
    }];
    layer.position = CGPointMake(0.0f, 0.0f);
    [CATransaction commit];
    
}

输出:

CoreAnimation之隐式动画_第4张图片
屏幕快照 2017-03-09 下午4.09.50.png

但是当我改变layer的默认行为的时候(就是注释的那部分),presentationLayer就不能获取到正确的值了

你可能感兴趣的:(CoreAnimation之隐式动画)