隐式动画这章我们介绍4个内容:事务、完成块、图层行为以及呈现与模型;
其实我们需要掌握的还有隐式动画的实现原理、UIKit怎样禁用了关联图层的隐式动画等等。
一、隐式动画的理解
Core Animation基于一个假设,说屏幕上的任何东西都可以(或者可能)做动画。动画并不需要打开,相反需要明确地关闭,否则他会一直存在。当你改变CALayer的一个可做动画的属性,它并不能立刻在屏幕上体现出来。相反,它是从先前的值平滑过渡到新的值;这一切都是默认的行为,你不需要做额外的操作,这就是隐式动画。
称为隐式动画的原因是我们没有指定动画的类型,只是改变了一个属性,然后Core Animation自己决定怎样去做动画,何时去做动画。
我们来用一个例子解释一下隐式动画:首先创建一个蓝色的方块,然后添加一个按钮,随机改变它的颜色。点击按钮,你会发现图层的颜色平滑过渡到一个新值,而不是跳变。这说明发生了一个隐式动画
二、隐式动画是怎样被实现的?
当我们修改属性时,CALayer自动应用的动画称作行为,当CALayer的属性被修改时,它会调用-actionForKey:方法,并传递属性名称。然后进行以下几步
(1)图层首先检查它是否有委托,如果有CALayerDelegate,就在委托中查找-actionForLayer:forKey:方法,如果有-actionForLayer:forKey:调用它并返回。
(2)如果没有委托或委托中没有实现-actionForLayer:forKey:方法,图层会检查actions字典,actions字典是属性名称对应行为的映射字典。
(3)如果actions字典中没有对应的属性名称,图层就检查style字典
(4)如果style中也没有对应的行为,那么图层将直接调用-defaultActionForKey:方法,
搜索完,-actionForKey:方法要么返回空(不会有动画发生),要么是CAAction协议对应的对象,然后CALayer拿这个结果对先前和当前的值做动画。
三、系统什么时候处理隐式动画
Core Animation在每个run loop周期中自动开始一次新的事务,任何在一次run loop循环中的属性的改变都会被集中起来,然后做一次0.25秒的动画
四、关于隐式动画的几点结论
(1)UIVIew扮演着它关联图层的代理,它直接在-actionForKey:forKey方法中返回nil来禁止了关联图层的隐式动画,对这种图层做动画的唯一办法就是使用UIView的动画函数(而不是依赖CATransaction),或者继承UIView,并覆盖-actionForLayer:forKey:方法,或者直接创建一个显式动画。
(2)CATransacition有个方法叫+setDisableActions:,可以用来对所有属性打开或者关闭隐式动画。
(3)对于单独存在的图层,我们可以通过实现图层的-actionForLayer:forKey:委托方法,或者提供一个actions字典来控制隐式动画。
下面介绍与隐式动画相关内容
一、事务
个人理解:这里的事务类似于数据库中的事务(作为一个逻辑单元执行的一系列操作)
我们用事务表示要完成的一个动画包含的一系列动作,即将属性的改变包含在事务中,避免产生混乱,使用事务我们可以修改系统的默认动画
(1)事务
事务通过CATransaction类来管理,事务实际上是Core Animation用来包含动画属性集合的机制,CATransaction类没有属性或实例方法,不能通过alloc、init方法创建它。但是可以通过+begin和+commit分别来将一个事务入栈或出栈;可以使用+setCompletionBlock:方法在某个动画结束时提供一个完成的动画
任何可以指定事务去管理的图层属性做动画时都不会立刻发生变化,而是事务提交后开始用一个动画过渡到新值,默认的动画时间是0.25秒,这就也就解释了隐式动画的出现了。
(2)使用事务自定义动画步骤
自定义动画不使用默认的动画,任何可以做动画的图层属性都可以指定一个事务来处理,在开始设置图层的动画属性之前使用+begin方法将新事务入栈(避免修改当前事务,产生副作用);然后设置动画属性,也可以通过+setAnimationDuration:方法来设置当前事务的动画时间,或通过+animationDuration来获取事务的动画时间等等;然后+commit提交新事务
(3)系统什么时候处理隐式动画
Core Animation在每个run loop周期中自动开始一次新的事务,即使你不显示的用[CATranscation begin]开始一次新事务,任何在一次run loop循环中的属性的改变都会被集中起来,然后做一次0.25秒的动画
二、完成块
CATranscation提供的+setCompletionlock:方法来实现在动画结束时添加一个完成的动作,类似UIView的block
UIView中的动画与图层的动画关系
(1)UIView有两个方法,`+beginAnimations:context:`和`+commitAnimations`,实现动画效果;实际上在`+beginAnimations:context:`和`+commitAnimations`之间所有视图或者图层属性的改变而做的动画都是由于设置了CATransaction的原因。
(2)UIView中基于block的动画方法:`+animateWithDuration:animations:`。这样写对做一堆的属性动画在语法上会更加简单,但实质上在`+animateWithDuration:animations:`内部调用了CATransaction的`+begin`和`+commit`方法,这样block中所有属性的改变都会被事务所包含。这样也可以避免开发者由于对+begin和+commit匹配的失误造成的风险。
//按钮事件
-(void)tapBtn{
//开始一个新事物
[CATransaction begin];
//设置动画时间持续到1秒钟
[CATransaction setAnimationDuration:2.0];
//setCompletionBlock完成块,增加一个旋转,
[CATransaction setCompletionBlock:^{
CGAffineTransform transform = colorLayer.affineTransform;
transform = CGAffineTransformRotate(transform, M_PI_2);
colorLayer.affineTransform = transform;
}];
//随机改变图层背景的颜色
CGFloat red = arc4random()/(CGFloat)INT_MAX;
CGFloat green = arc4random()/(CGFloat)INT_MAX;
CGFloat blue = arc4random()/(CGFloat)INT_MAX;
colorLayer.backgroundColor=[UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
//提交事务
[CATransaction commit];
}
- 旋转动画比颜色渐变动画快的多,这是因为完成块是在颜色渐变的事务提交并出栈之后才被执行的,于是,旋转是用默认的事务做变换,默认时间是0.25秒。
三、图层行为
(1)介绍
行为通常是一个被Core Animation隐式调用的显示动画对象,我们使用CATransaction的实例,叫做推进过渡。以后会详细解释过渡,不过对于现在,知道CATransition响应CAAction协议,定义图层的行为,例如下面自定义了一个行为,不论在什么时候改变背景颜色,新的色块都是从左侧滑入,而不是默认的渐变效果。(CATransition是一个动画(一般完成过渡动画),而CATransaction是一个动画事务)
例子:例子实现的功能,改变图层的背景色,并且每次改变的背景色好像另一个颜色的图层从左边推入
//创建图层
colorLayer = [CALayer layer];
colorLayer.frame = CGRectMake(50, 50, 100, 100);
colorLayer.backgroundColor = [UIColor blueColor].CGColor;
[layerView.layer addSublayer:colorLayer];
//给图层添加一个自定义的行为
CATransition *transition =[CATransition animation];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromLeft;
colorLayer.actions = @{@"backgroundColor":transition};
(2)UIKit禁用关联图层的隐式动画的证明例子
例子:设置UIView关联的图层的颜色进行变换,但是结果与之前使用单独的图层,变换图层的颜色结果不同;这里的颜色是立即变化的,没有平滑的过度,下面是例子的代码
@interface layerBehaviorVC ()
{
UIView *layerView;
}
@end
@implementation layerBehaviorVC
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor lightGrayColor];
layerView = [[UIView alloc]initWithFrame:CGRectMake(100, 100, 200, 200)];
layerView.backgroundColor = [UIColor whiteColor];
[self.view addSubview:layerView];
//创建按钮,改变颜色
UIButton *btn = [[UIButton alloc]initWithFrame:CGRectMake(100, 350, 200, 80)];
[btn setTitle:@"change color" forState:UIControlStateNormal];
[self.view addSubview:btn];
[btn addTarget:self action:@selector(tapBtn) forControlEvents:UIControlEventTouchUpInside];
}
//按钮事件
-(void)tapBtn{
//开始一个新事物
[CATransaction begin];
//设置动画时间持续到1秒钟
[CATransaction setAnimationDuration:2.0];
//随机改变图层背景的颜色
CGFloat red = arc4random()/(CGFloat)INT_MAX;
CGFloat green = arc4random()/(CGFloat)INT_MAX;
CGFloat blue = arc4random()/(CGFloat)INT_MAX;
//应用的view上,即应用到view的关联图层上
layerView.layer.backgroundColor=[UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
//提交事务
[CATransaction commit];
}
当使用UIView的图层设置渐变颜色时,图层的颜色是瞬间切换到新值的,而不是之前在colorLayer上显示的平滑的过渡的动画。
答案是UIView禁用了它关联图层的引用动画。
那么UIKit如何禁用隐式动画的?
我们知道了隐式动画的实现原理,而且每个UIView对它的关联图层而言相当于一个委托,并且实现了-actionForLayer:forKey
方法。在动滑block的范围内,-actionForLayer:forKey返回非空值,不在动画块的实现中,UIView对所有的图层行为返回nil。我们通过例子测试一下UIView的actionForLayer:forKey的实现
layerView = [[UIView alloc]initWithFrame:CGRectMake(100, 100, 200, 200)];
layerView.backgroundColor = [UIColor whiteColor];
[self.view addSubview:layerView];
//测试在动画块外图层的行为action
NSLog(@"outside:%@",[layerView actionForLayer:layerView.layer forKey:@"backgroundColor"]);
//开始动画块
[UIView beginAnimations:nil context:nil];
//在动画块内测试图层的action
NSLog(@"inside:%@",[layerView actionForLayer:layerView.layer forKey:@"backgroundColor"]);
//结束动画块
[UIView commitAnimations];
- 输出结果
outside:<null>
inside:<CABasicAnimation: 0x7fb581fba4c0>
由上面例子可以看出,当在动画块外修改属性的值时,UIVIew直接在-actionForKey:forKey方法中返回nil来禁止隐式动画。另一种禁用隐式动画的方法,CATransacition有个方法叫+setDisableActions:,可以用来对所有属性打开或者关闭隐式动画。
四、呈现与模型
改变CALayer的属性并没有立刻生效,而是通过一段时间渐变更新。下面我们讲探讨这是怎么做到的?
当我们改变CALayer的一个属性时,CALayer的属性值是立马改变的,只是我们改变的属性没有直接调整图层的外观,它只是定义了图层动画结束之后图层如何显示的模型。其实这里涉及到一个微型的MVC模式。
——Core Animation类似控制器,负责根据图层行为和事务的设置去不断的更新屏幕上这些属性的状态
——CALayer类似模型,它是连接用户界面的虚构的类,存储了视图如何显示和动画的数据模型。apple文档中写有”图层树通常都是值的图层树模型”
——呈现图层,是模型图层的复制,呈现图层上的属性值代表了当前屏幕显示的外观效果的属性的值,可以使用-presentationLayer方法来获取当前屏幕上属性的真正显示的值
呈现图层是由呈现树中的所有图层的的呈现图层所形成。呈现图层是在图层首次在屏幕上显示的时候创建的。如果在创建之前调用图层的-presentationLayer将会返回nil。在呈现图层上调用-modelLayer方法会返回它所呈现的图层的依赖图层(这就可以说明我们创建的原始图层是一种数据模型)
由MVC模式,我们可以了解,大多数情况下。我们不需要直接访问呈现图层,我们可以通过和模型图层(也就是我们创建的图层)交互,让Core Animation来更新显示呈现图层。下面两种情况利用呈现图层更方便
(1)同步动画:实现基于定时器的动画时(不是基于事务的动画),使用呈现图层可以准确的知道某一时刻图层的显示位置,有利于正确的摆放图层
(2)处理用户交互:想让做动画的图层响应用户的输入时,在呈现图层上使用-hitTest:方法来判断指定图层是否被触摸。因为呈现图层代表了用户当前看到的图层位置,而不是当前动画结束之后的位置。