CALayer的隐式动画
当你改变
CALayer
的一个可做动画的属性(例如backgroundColor
背景颜色),它并不能立刻在屏幕上体现出来。相反,它是从先前的值平滑过渡到新的值(动画)。这一切都是默认的行为,你不需要做额外的操作。
CATransaction事务
CALayer
的隐式动画通过CATransaction
事务实现
- 如果你用过
UIView
的动画方法做过一些动画效果,那么应该对这个模式不陌生。UIView
有两个方法,+beginAnimations:context:
和+commitAnimations
,和CATransaction
的+begin
和+commit
方法类似。 - 实际上在
+beginAnimations:context:
和+commitAnimations
之间所有视图或者图层属性的改变而做的动画都是由于设置了CATransaction
的原因。 - 在
iOS4
中,苹果对UIView
添加了一种基于block
的动画方法:+animateWithDuration:animations:
。这样写对做一堆的属性动画在语法上会更加简单,但实质上它们都是在做同样的事情。 CATransaction
的+begin
和+commit
方法在+animateWithDuration:animations:
内部自动调用,这样block
中所有属性的改变都会被事务所包含。这样也可以避免开发者由于对+begin
和+commit
匹配的失误造成的风险。- 基于
UIView
的block
的动画允许你在动画结束的时候提供一个完成的动作。CATransaction
接口提供的+setCompletionBlock:
方法也有同样的功能。
// 开启事务
[CATransaction begin];
// 设置动画时间
[CATransaction setAnimationDuration:1.0];
// 执行完成后调用
[CATransaction setCompletionBlock:^{
// 旋转90度
CGAffineTransform transform = self.colorLayer.affineTransform;
transform = CGAffineTransformRotate(transform, M_PI_2);
self.colorLayer.affineTransform = transform;
}];
// 随机颜色
CGFloat red = arc4random() / (CGFloat)INT_MAX;
CGFloat green = arc4random() / (CGFloat)INT_MAX;
CGFloat blue = arc4random() / (CGFloat)INT_MAX;
// 修改Layer的背景颜色
self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
// 提交事务
[CATransaction commit];
图层行为(默认禁用了CALayer的隐式动画)
CATransaction
事务设置UIView
关联layer
的背景色动画失效
// ⚠️该例子无动画效果,CATransaction对直接设置UIView的Layer层动画失效
// 开启事务
[CATransaction begin];
// 设置动画时间
[CATransaction setAnimationDuration:1.0];
// 随机颜色
CGFloat red = arc4random() / (CGFloat)INT_MAX;
CGFloat green = arc4random() / (CGFloat)INT_MAX;
CGFloat blue = arc4random() / (CGFloat)INT_MAX;
self.layerView.layer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
// 提交事务
[CATransaction commit];
UIKit
是如何禁用隐式动画- 每个
UIView
对它关联的图层都扮演了一个委托,并且提供了-actionForLayer:forKey
的实现方法。当不在一个动画块的实现中,UIView
对所有图层行为返回nil
,但是在动画block
范围之内,它就返回了一个非空值
// 输出UIView的 actionForLayer:forKey:方法
NSLog(@"Outside: %@", [self.layerView actionForLayer:self.layerView.layer forKey:@"backgroundColor"]);
// 开始动画
[UIView beginAnimations:nil context:nil];
// 输出UIView的 actionForLayer:forKey:方法
NSLog(@"Inside: %@", [self.layerView actionForLayer:self.layerView.layer forKey:@"backgroundColor"]);
// 结束动画
[UIView commitAnimations];
- 在动画
block
范围之内UIView
返回CABasicAnimation
动画类型
控制台输出:
$ LayerTest[21215:c07] Outside: <null>
$ LayerTest[21215:c07] Inside: <CABasicAnimation: 0x757f090>
- 当然返回
nil
并不是禁用隐式动画唯一的办法,CATransacition
有个方法叫做+setDisableActions:
,可以用来对所有属性打开或者关闭隐式动画
// 在使用CATransaction时添加,可以阻止动画发生
[CATransaction setDisableActions:YES];
总结
UIView
关联的图层禁用了隐式动画,对这种图层做动画的唯一办法就是使用UIView
的动画函数(而不是依赖CATransaction
),或者继承UIView
,并覆盖-actionForLayer:forKey:
方法,或者直接创建一个显式动画- 对于单独存在的图层,我们可以通过实现图层的
-actionForLayer:forKey:
委托方法,或者提供一个actions
字典来控制隐式动画
// 通过actions字典控制隐式动画
CALayer *colorLayer = [CALayer layer];
colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
colorLayer.backgroundColor = [UIColor blueColor].CGColor;
// Layer添加CATransition
CATransition *transition = [CATransition animation];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromLeft;
colorLayer.actions = @{
@"backgroundColor": transition};
// UIView添加Layer
UIView *layerView = [[UIView allow] init];
[layerView.layer addSublayer:self.colorLayer];
// 这个时间修改Layer层的backgroundColor,自带动画
呈现与模型
- 当你改变一个图层的属性,属性值的确是立刻更新的(如果你读取它的数据,你会发现它的值在你设置它的那一刻就已经生效了),但是屏幕上并没有马上发生改变。这是因为你设置的属性并没有直接调整图层的外观,相反,他只是定义了图层动画结束之后将要变化的外观
- 当设置
CALayer
的属性,实际上是在定义当前事务结束之后图层如何显示的模型 presentationLayer
:它的属性值代表了在任何指定时刻当前外观效果。换句话说,你可以通过呈现图层的值来获取当前屏幕上真正显示出来的值,presentationLayer
是被提交(就是首次第一次在屏幕上显示)的时候创建,所以在那之前调用presentationLayer
将会返回nil
hitTest
:方法来判断指定图层是否被触摸,这时候对呈现图层而不是模型图层调用hitTest
会显得更有意义,因为呈现图层代表了用户当前看到的图层位置,而不是当前动画结束之后的位置