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;翻译过来大概就是说:
图层会首先检测它是否有委托,并且是否实现CALayerDelegate协议指定的-actionForLayer:forKey方法;如果有,就直接调用并返回结果。如果没有委托或者委托没有实现-actionForLayer:forKey方法,图层将会检查包含属性名称对应行为映射的actions字典如果actions字典没有包含对应的属性,图层接着在它的style字典里搜索属性名.最后,如果在style也找不到对应的行为,那么图层将会直接调用定义了每个属性的标准行为的+defaultActionForKey:方法从流程上分析来看,经过一次完整的搜索动画之后,-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关联的图层禁用了隐式动画,那么对这种图层做动画的方法有有了以下几种方式:
使用UIView的动画函数(而不是依赖CATransaction)继承UIView,并覆盖-actionforLayer:forkey:方法直接创建显式动画其实,对于单独存在的图层,我们也可以通过实现图层的-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字典实现了自定义的图层行为;