参考:https://zsisme.gitbooks.io/ios-/content/
前面的文章动画基础--基于Core Animation(1),动画基础--基于Core Animation(2)提到了图层的基本概念以及可动画参数几何学等知识。
本片文章将继续探讨更加深入的动画知识。
前面已经两篇文章已经就图层的概念和几何学和效果表现,变换等左列介绍。
在前面的两篇文章中大部分采用了视图自带的backing 图层进行讲解,本身这个图层是禁止动画效果的。所以在前面的文章中很少看到明显的
动画,在接下来的总结文章中将介绍隐式动画和显示动画。
首先需要总结的是:
1.视图的本身是没有动画概念的,它的动画的接口和效果的产生其实是对于其内部图层的处理。
2.不是所有图层的属性都具有动画效果,只有可动画属性才能具有动画效果。
以下为常用的图层可动画属性
1.隐式动画
简单的隐式动画:
当你改变CALayer的一个可做动画的属性,它并不能立刻在屏幕上体现出来。相反,它是从先前的值平滑过渡到新的值。这一切都是默认的行为,你不需要做额外的操作。
隐式动画在图层中是默认开启的,如果不需要隐式动画则需要主动去关闭它。
#pragma 测试简单动画 -(void)testSampleAnimation { mySampleLayer = [CALayer layer]; mySampleLayer.backgroundColor = [UIColor blackColor].CGColor; mySampleLayer.frame = CGRectMake(50, 50, 150, 150); [self.view.layer addSublayer:mySampleLayer]; } - (void)testSampleAnimationTouch { mySampleLayer.backgroundColor = [UIColor redColor].CGColor; }
上述代码中只是创建了一个图层,然后改变了图层的颜色从黑道红,我们能够感觉到颜色的变化并不是瞬间的,而是有一个过度。
即使你不显式的用[CATransaction begin]开始一次事务,任何在一次run loop循环中属性的改变都会被集中起来,然后做一次0.25秒的动画。
2.动画事务
通过CATransaction的+begin和+commit分别来入栈或者出栈来控制动画的事务。通过这个事务我们能够控制动画的持续事件等。
// 动画开始 [CATransaction begin]; // 事务其他设定 [CATransaction ..]; // 动画效果代码 // 动画提交 [CATransaction commit];
如下代码,我们将改变颜色的动画事件持物2秒
- (void)testSampleAnimationTouch { // 动画开始 [CATransaction begin]; // 动画将持续2秒 [CATransaction setAnimationDuration:2.0]; // 动画效果代码 mySampleLayer.backgroundColor = [UIColor redColor].CGColor; // 动画提交 [CATransaction commit]; }
我们明显感觉到颜色的变换过度持续的更久了。
我们在使用UIView的时候是使用了block的方式,其实内部也是自动帮助我们使用了图层的事务机制来控制的。
3.完成块
在UIView的动画总我们会设定一个block,当动画完毕的时候去触发,同样的图层的事务中也会存在这样的行为
// 事务完成块 CATransaction setCompletionBlock:^{ <#code#> }
如下代码,在2秒背景颜色后将图层旋转90度
- (void)testSampleAnimationTouch { // 动画开始 [CATransaction begin]; // 动画将持续2秒 [CATransaction setAnimationDuration:2.0]; // 事务完成块 [CATransaction setCompletionBlock:^{ CGAffineTransform transform = mySampleLayer.affineTransform; transform = CGAffineTransformRotate(transform, M_PI_2); mySampleLayer.affineTransform = transform; }]; // 动画效果代码 mySampleLayer.backgroundColor = [UIColor redColor].CGColor; // 动画提交 [CATransaction commit]; }
结果发现,颜色还是按照2秒事件渐变,但是旋转却完成的非常快,这是因为完成块并不在事务中,所以默认时间为0.25秒。
4.图层行为:
我们把改变属性时CALayer自动应用的动画称作行为
4.1图层行为机制:
当CALayer的属性被修改时候,它会调用-actionForKey:方法,传递属性的名称。剩下的操作都在CALayer的头文件中有详细的说明,
实质上是如下几步:
- 图层首先检测它是否有委托,并且是否实现CALayerDelegate协议指定的-actionForLayer:forKey方法。如果有,直接调用并返回结果。
- 如果没有委托,或者委托没有实现-actionForLayer:forKey方法,图层接着检查包含属性名称对应行为映射的actions字典。
- 如果actions字典没有包含对应的属性,那么图层接着在它的style字典接着搜索属性名。
- 最后,如果在style里面也找不到对应的行为,那么图层将会直接调用定义了每个属性的标准行为的-defaultActionForKey:方法。
我们常常提到UIView的图层已经被禁止了动画,然后我们又可以通过UIView的block或者事务去做简单的动画效果,两者听上去非常的矛盾。其实秘密就在于:
UIView的图层的代理即为UIView本身,实现了-actionForLayer:forKey方法,但是在此方法中它判断了图层可动画属性的变动是否是在动画block或者动画事务中
发生的,如果是则返回action也就可以具有动画表现,如果不是则返回nil,这样就不会产生动画属性。
证明:如下代码中分别在外部和动画块内部调用了-actionForLayer:forKey方法
#pragma UIView的动画机制 -(void) testUIViewAnimation { UIView *myView = [[UIView alloc]init]; NSLog(@"outer: %@", [myView actionForLayer:myView.layer forKey:@"backgroundColor"]); [UIView animateWithDuration:2.0 animations:^{ NSLog(@"inner: %@", [myView actionForLayer:myView.layer forKey:@"backgroundColor"]); }]; }
结果:对于backgroundColor属性,在外部是得到了nil,这就是我们所说的UIView的图层禁止了隐式动画的原因。而在动画块内部则能够得到动画action,也就是在这里修改可动画属性的话则
可以有动画效果。
2016-05-12 16:33:40.970 TestCoreAnim[1067:41601] outer: <null> 2016-05-12 16:33:40.972 TestCoreAnim[1067:41601] inner: <CABasicAnimation: 0x7a956150>
上述方法我们可以禁止隐式动画我们也可以通过事务的方法来禁止
[CATransaction setDisableActions:YES];
4.2 控制图层行为:
- UIView关联的图层禁用了隐式动画,对这种图层做动画的唯一办法就是使用UIView的动画函数(而不是依赖CATransaction),或者继承UIView,并覆盖-actionForLayer:forKey:方法,或者直接创建一个显式动画(具体细节见第八章)。
- 对于单独存在的图层,我们可以通过实现图层的-actionForLayer:forKey:委托方法,或者提供一个actions字典来控制隐式动画。
如下测试中改变背景颜色,新的色块都是从左侧滑入:
-(void) changeLayerAction { //create sublayer mySampleLayer = [CALayer layer]; mySampleLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f); mySampleLayer.backgroundColor = [UIColor blueColor].CGColor; //add a custom action CATransition *transition = [CATransition animation]; transition.type = kCATransitionPush; transition.subtype = kCATransitionFromLeft; mySampleLayer.actions = @{@"backgroundColor": transition}; //add it to our view [self.view.layer addSublayer:mySampleLayer]; }
结果:单独存在的图层由于没有指定代理,那么他将直接去自身的actions字典中寻找搜索是否有指定的属性被修改,和指定属性的行为定义。
我们看到图层的颜色不再是默认的渐变成黑色,而是黑色块会从左到右挤掉现有颜色。
5. 呈现与模型
当我们设定一个可动画参数如图层的position的时候,其实一旦设定完毕后position已经是新的值了,但是在屏幕上我们会看到图层是“缓慢”地移动的新的position位置。
我们把这个“运动中的”图层称之为呈现图层,正如其名,它正是呈现在屏幕上用户得以观察到的图层。而参数改变前已经动画结束后的图层我们称之为模型图层,他们的关系是:
每个图层属性的显示值都被存储在一个叫做呈现图层的独立图层当中,他可以通过-presentationLayer方法来访问。这个呈现图层实际上是模型图层的复制,但是它的属性值代表了
在任何指定时刻当前外观效果。换句话说,你可以通过呈现图层的值来获取当前屏幕上真正显示出来的值,在换句话说即使图层静止不动了,那么这个方法返回的图层即使本身。
5.1 何时需要用到呈现图层
- 如果你在实现一个基于定时器的动画,而不仅仅是基于事务的动画,这个时候准确地知道在某一时刻图层显示在什么位置就会对正确摆放图层很有用了。
- 如果你想让你做动画的图层响应用户输入,你可以使用-hitTest:方法来判断指定图层是否被触摸,这时候对呈现图层而不是模型图层调用-hitTest:会显得更有意义,因为呈现图层代表了用户当前看到的图层位置,而不是当前动画结束之后的位置。
例子:下面的代码中我们我们创建了一个图层,当在点击图层的时候就随机变换颜色,当积极到其他位置的时候就会缓慢移动到点击位置。
#pragma 呈现图层 - (void) testPresentLayer { //create a red layer mySampleLayer = [CALayer layer]; mySampleLayer.frame = CGRectMake(0, 0, 100, 100); mySampleLayer.position = CGPointMake(self.view.bounds.size.width / 2, self.view.bounds.size.height / 2); mySampleLayer.backgroundColor = [UIColor redColor].CGColor; [self.view.layer addSublayer:mySampleLayer]; } -(void)testPresentLayerTouch:(NSSet<UITouch *> *)touches { //get the touch point CGPoint point = [[touches anyObject] locationInView:self.view]; //check if we‘ve tapped the moving layer if ([mySampleLayer.presentationLayer hitTest:point]) { //randomize the layer background color CGFloat red = arc4random() / (CGFloat)INT_MAX; CGFloat green = arc4random() / (CGFloat)INT_MAX; CGFloat blue = arc4random() / (CGFloat)INT_MAX; mySampleLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor; } else { //otherwise (slowly) move the layer to new position [CATransaction begin]; [CATransaction setAnimationDuration:4.0]; mySampleLayer.position = point; [CATransaction commit]; } }
结果:
总结:上述讲述了隐式动画。