Core Animation的一个非常显著的特性是就是实现动画,而且它支持隐式动画和显式动画两种形式,本篇我们主要从隐式动画说起;
本篇主要内容: 1.何为隐式动画 2.隐式动画原理-事务与图层行为 3.隐式动画的关闭与显示 4.隐式动画自定义图层行为
Core Animation是基于这样的一个假设:屏幕上的任何东西都可以(或者可能)做动画,它并不需要手动打开,反而是需要我们明确的关闭,否则动画会一直存在。所谓隐式动画,其实是指我们可以在不设定任何动画类型的情况下,仅仅改变CALayer的一个可做动画的属性,就能实现动画效果。 这听起来似乎不太真实,我们可以通过下面的代码来验证,使用随机色修改了CALayer的背景色:
@interface TestLayerAnimationVC ()
@property (nonatomic,strong) CALayer *colorLayer;
@end
- (void)viewDidLoad {
[super viewDidLoad];
_colorLayer = [[CALayer alloc] init];
_colorLayer.frame = CGRectMake(30, 30, kDeviceWidth - 60, 60);
[self.view.layer addSublayer:_colorLayer];
}
- (IBAction)changeColor:(UIButton *)sender{
CGFloat red = arc4random() % 255 / 255.0;
CGFloat green = arc4random() % 255 / 255.0;
CGFloat blue = arc4random() % 255 / 255.0;
UIColor *randomColor = [UIColor colorWithRed:red green:green blue:blue alpha:1];
_colorLayer.backgroundColor = randomColor.CGColor;
}
效果图如下:
测试隐式动画.gif
经过测试,我们会发现每次设置的颜色并不是立刻在屏幕上跳变出来,相反,它是从先前的值平滑过渡到新的值,这一切都是默认行为,你不需要做额外的操作,这就是隐式动画。
当我们改变一个CALayer属性时,Core Animation是如何判断动画类型和持续时间呢?实际上动画执行的时间取决于当前事务的设置,动画类型则取决于图层行为。
事务,其实是Core Animation用来包含一系列属性动画集合的机制,通过指定事务来改变图层的可动画属性,这些变化都不是立刻发生变化的,而是在事务被提交的时候才启动一个动画过渡到新值。任何可以做动画的图层属性都会被添加到栈顶的事务。
事务是通过CATransaction类来做管理,它没有属性或者实例方法,而且也不能通过alloc和init去创建它,它的常用操作如下:
//1.动画属性的入栈
+ (void)begin;
//2.动画属性出栈
+ (void)commit;
//3.设置当前事务的动画时间
+ (void)setAnimationDuration:(CFTimeInterval)dur;
//4.获取当前事务的动画时间
+ (CFTimeInterval)animationDuration;
//5.在动画结束时提供一个完成的动作
+ (void)setCompletionBlock:(nullable void (^)(void))block;
现在再来考虑隐式动画,其实是Core Animation在每个RunLoop周期中会自动开始一次新的事务,即使你不显式的使用[CATranscation begin]开始一次事务,任何在一次RunLoop运行时循环中属性的改变都会被集中起来,执行默认0.25秒的动画。 现在,我们就通过事务来设置动画做一个验证,代码如下:
- (IBAction)changeColor:(UIButton *)sender{
[CATransaction begin]; //入栈
//1.设置动画执行时间
[CATransaction setAnimationDuration:3];
//2.设置动画执行完毕后的操作:颜色渐变之后再旋转90度
[CATransaction setCompletionBlock:^{
CGAffineTransform transform = self.colorLayer.affineTransform;
transform = CGAffineTransformRotate(transform, M_PI_2);
self.colorLayer.affineTransform = transform;
}];
CGFloat red = arc4random() % 255 / 255.0;
CGFloat green = arc4random() % 255 / 255.0;
CGFloat blue = arc4random() % 255 / 255.0;
UIColor *randomColor = [UIColor colorWithRed:red green:green blue:blue alpha:1];
_colorLayer.backgroundColor = randomColor.CGColor;
[CATransaction commit]; //出栈
}
效果图如下:
测试隐式动画事务.gif
可以看到,CALayer颜色的渐变动画已经变为了3秒,而旋转动画由于是默认事务变化,仍然以0.25秒快速执行。
我们上述的实验对象是一个独立图层,如果直接对UIView或者CALayer关联的图层layer改变动画属性,这样是没有隐式动画效果的,这说明虽然Core Animation对所有的CALayer动画属性设置了隐式动画,但UIView把它关联的图层的这个特性给关闭了。 为了更好的理解中一点,我们需要知道隐式动画是如何实现的: 我们把改变属性时CALayer自动执行的动画称作行为,当CALayer的属性被修改时,它会调用-actionForKey:方法传递属性名称,我们可以找到这个方法的具体说明如下:
/* Returns the action object associated with the event named by the
* string 'event'. The default implementation searches for an action
* object in the following places:
*
* 1. if defined, call the delegate method -actionForLayer:forKey:
* 2. look in the layer's `actions' dictionary
* 3. look in any `actions' dictionaries in the `style' hierarchy
* 4. call +defaultActionForKey: on the layer's class
*
* If any of these steps results in a non-nil action object, the
* following steps are ignored. If the final result is an instance of
* NSNull, it is converted to `nil'. */
- (nullable id<CAAction>)actionForKey:(NSString *)event;
翻译过来大概就是说:
从流程上分析来看,经过一次完整的搜索动画之后,-actionForKey:要么返回空(这种情况不会有动画发生),要么返回遵循CAAction协议的对象(CALayer拿这个结果去对先前和当前的值做动画)。现在我们再来考虑UIKit是如何禁用隐式动画的: 每个UIView对它关联的图层都遵循了CALayerDelegate协议,并且实现了-actionForLayer:forKey方法。当不在一个动画块中修改动画属性时,UIView对所有图层行为都返回了nil,但是在动画Block范围就返回了非空值,下面通过一段代码来验证:
@interface TestLayerAnimationVC ()
@property (nonatomic,weak)IBOutlet UIView *layerView;
@end
- (void)viewDidLoad {
[super viewDidLoad];
//测试图层行为:UIKit默认关闭了自身关联图层的隐式动画
NSLog(@"OutSide:%@",[self.layerView actionForLayer:self.layerView.layer forKey:@"backgroundColor"]);
[UIView beginAnimations:nil context:nil];
NSLog(@"InSide:%@",[self.layerView actionForLayer:self.layerView.layer forKey:@"backgroundColor"]);
[UIView commitAnimations];
}
//打印:
OutSide:<null>
InSide:<CABasicAnimation: 0x600001703100>
由此得出结论:当属性在动画块之外发生变化,UIView直接通过返回nil来禁用隐式动画。但是如果在动画块范围内,UIView则会根据动画具体类型返回响应的属性,
当然,返回nil并不是禁用隐式动画的唯一方法,CATransaction也为我们提供了具体的方法,可以用来对所有属性打开或者关闭隐式动画,方法如下:
+ (void)setDisableActions:(BOOL)flag;
UIView关联的图层禁用了隐式动画,那么对这种图层做动画的方法有有了以下几种方式:
其实,对于单独存在的图层,我们也可以通过实现图层的-actionforLayer:forkey:方法,或者提供一个actions字典来控制隐式动画
通过对事务和图层行为的了解,我们可以这样思考,图层行为其实是被Core Animation隐式调用的显式动画对象。我们可以发现改变隐式动画的这种图层行为有两种方式: 1.给layer设置自定义的actions字典 2.实现委托代理,返回遵循CAAction协议的动画对象 现在,我们尝试使用第一种方法来自定义图层行为,这里用到的是一个推进过渡的动画(也是遵循了CAAction的动画类),具体的代码如下:
@interface TestLayerAnimationVC ()
@property (nonatomic,strong) CALayer *colorLayer;
@end
- (void)viewDidLoad {
[super viewDidLoad];
_colorLayer = [[CALayer alloc] init];
_colorLayer.frame = CGRectMake(30, 30, kDeviceWidth - 60, 60);
_colorLayer.backgroundColor = [UIColor orangeColor].CGColor;
//自定义动画对象
CATransition *transition = [CATransition animation];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromLeft;
_colorLayer.actions = @{@"backgroundColor":transition};
[self.view.layer addSublayer:_colorLayer];
}
- (IBAction)changeColor:(UIButton *)sender{
CGFloat red = arc4random() % 255 / 255.0;
CGFloat green = arc4random() % 255 / 255.0;
CGFloat blue = arc4random() % 255 / 255.0;
UIColor *randomColor = [UIColor colorWithRed:red green:green blue:blue alpha:1];
_colorLayer.backgroundColor = randomColor.CGColor;
}
效果图如下:
测试隐式动画-自定义图层行为.gif
经测试,我们会看到colorLayer将会以从左到右推进过渡的形式改变色值;我们通过给layer设置自定义的actions字典实现了自定义的图层行为;