iOS Core Animation Advanced Techniques-隐式动画
上六章节:
这篇随笔主要介绍有关图层隐式动画。
隐式动画:
- 没有指定任何动画类型,而改变了一个属性,Core Animation决定如何并且何时去做动画。
- 动画执行的事件取决于当前事务的设置;
- 动画类型取决于图层行为。
- Core Animation假设屏幕上任何东西都可能做动画,默认动画效果是打开的。
- 当CALayer的一个 可做动画的 属性 被改变,默认从先前值平滑过渡到新值,而不是立刻显示新值在屏幕上,因此携带了隐式动画。
- 示范例子:
- myLayer=[CALayer layer];
- myLayer.frame=CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
- myLayer.backgroundColor=[UIColor blueColor].CGColor;
- [myView.layer addSublayer:myLayer];
- -(void)changeColor{
- CGFloat red = arc4random() / (CGFloat)INT_MAX;
- CGFloat green = arc4random() / (CGFloat)INT_MAX;
- CGFloat blue = arc4random() / (CGFloat)INT_MAX;
- myLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
- }
事务:
- Core Animation用来包含一些列属性动画集合的机制,用指定事务去改变 可做动画的 图层属性,都不会立刻发生变化,而是当事务提交的时候开始用一个动画过渡到新值
- 事务通过CATransaction类来做管理:
- 该类没有属性或者实例方法,不能用+alloc与-init方法创建
- 可以使用+begin(起一个新事务,对应UIView中的做动画方法+beginAnimations:context:)与+commit(提交一个事务,对应UIView中+commitAnimations的做动画方法)分别入栈或者出栈(iOS4后,UIView做动画的方法新增+animateWithDuration:animations:把begin与commit方法一起封装到该方法内)
- 可以通过+setAnimationDuration:方法设置当前事务的动画时间,为了不影响别的动画,一般使用该方法前都是在一个独立的事务中设置(begin与commit之间)
- 可以通过+animationDuration方法获得动画时间值(默认0.25s)
- 示范例子:
- -(void)changeColor{
- [CATransaction begin];
- [CATransaction setAnimationDuration:1.0];
- CGFloat red = arc4random() / (CGFloat)INT_MAX;
- CGFloat green = arc4random() / (CGFloat)INT_MAX;
- CGFloat blue = arc4random() / (CGFloat)INT_MAX;
- myLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
- [CATransaction commit];
- }
- 完成块:
- CATranscation接口提供+setCompletionBlock:方法实现UIView中做动画的 动画结束的回调block块
- 示范例子:
- -(void)changeColor{
- [CATransaction begin];
- [CATransaction setAnimationDuration:1.0];
- [CATransaction setCompletionBlock:^{
- CGAffineTransform transform = self.colorLayer.affineTransform;
- transform = CGAffineTransformRotate(transform, M_PI_2);
- myLayer.affineTransform = transform;
- }];
- CGFloat red = arc4random() / (CGFloat)INT_MAX;
- CGFloat green = arc4random() / (CGFloat)INT_MAX;
- CGFloat blue = arc4random() / (CGFloat)INT_MAX;
- myLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
- [CATransaction commit];
- }
图层行为:
- 行为:
- CALayer的属性改变时,CALayer自动应用的动画
- 当CALayer属性被改变时,会调用-actionForKey:方法,传递属性的名称
- 实质上如下几步:
1.CALayer先检测它是否有委托,并且是否实现CALayerDelegate协议指定的-actionForLayer:forKey方法,有则直接调用并返回结果
2.如果没有委托或委托没有实现-actionForLayer:forKey方法,CALayer接着检查包含属性名称对应行为映射的actions字典
3.如果action字典没有包含对应的属性,那么CALayer接着在它的style字典接着搜索属性名
4.如果在style也找不到对应行为,那么图层将会直接调用定义了每个属性的标准行为的-defaultActionForKey:方法
5.最后搜索结束后,-actionForKey:要么返回空(不会有动画发生),要么是CAAction协议对应的对象,最后CALayer拿这个结果对属性旧值与新值做动画。
-
- 以上是对一个单独的图层做属性改变,会看到隐式动画的发生,
- 若直接对UIView关联的图层做属性改变,那么在屏幕上呈现的结果将会是马上切换到新值,而不是之前平滑过渡的动画
- 因为UIView把关联的图层的隐式动画特性关闭了。
- 每个UIView对于它关联的图层都扮演了一个委托,并提供了-actionForLayer:forKey的实现方法,当不在一个动画块的实现中,UIView对所有图层行为返回nil,若在动画block范围内,就返回一个非空值
- 示范例子:(UIView的actionForLayer:forKey:实现)
- - (void)viewDidLoad{
- [super viewDidLoad];
- 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];
- }
- /*程序运行结果:
- $ LayerTest[21215:c07] Outside: <null>
- $ LayerTest[21215:c07] Inside: <CABasicAnimation: 0x757f090>
- 结论:当属性在动画块之外发生改变,UIView直接通过返回nil来禁用隐式动画。但如果在动画块范围之内,根据动画具体类型返回相应的属性,在这个例子就是CABasicAnimation*/
- 以上通过设置代理并且实现代理方法返回nil禁用CALayer的隐式动画
- 还有另外一个禁用方法就是:
- 通过CATransaction的+setDisableActions:方法,对所有属性打开或关闭隐式动画
- 如:[CATransaction setDisableActions:YES];
- 小结:
- 1.UIView关联的图层禁用了隐式动画,对这种图层做动画的唯一办法就是使用UIView的动画函数(而不是依赖CATransaction),或者继承UIView,覆盖-actionForLayer:forKey:方法,或者直接创建一个显示动画
- 2.对于单独存在的图层,可以通过实现图层的-actionForLayer:forKey:委托方法,或者提供一个actions字典控制隐式动画
- 改变默认隐式动画实现自定义行为:
- 通过给独立图层CALayer设置一个自定义的actions字典,当然也可以使用委托实现,但是actions字典可以写更少的代码
- 使用例子:
- self.colorLayer = [CALayer layer];
- self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
- self.colorLayer.backgroundColor = [UIColor blueColor].CGColor;
- //add a custom action
- CATransition *transition = [CATransition animation];
- transition.type = kCATransitionPush;
- transition.subtype = kCATransitionFromLeft;
- self.colorLayer.actions = @{@"backgroundColor": transition};//重点
- //add it to our view
- [self.layerView.layer addSublayer:self.colorLayer];
- - (IBAction)changeColor{
- //randomize the layer background color
- CGFloat red = arc4random() / (CGFloat)INT_MAX;
- CGFloat green = arc4random() / (CGFloat)INT_MAX;
- CGFloat blue = arc4random() / (CGFloat)INT_MAX;
- self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
- }
- 呈现与模型:
- 当CALayer的属性被改变时,属性值的确是立刻更新的,但是屏幕上并没有马上发生改变,
- 这是因为设置的属性并没有直接调整图层的外观,只是定义了图层动画结束后将要变化的外观
- CALayer的属性,好比模型;Core Animation扮演一个控制器;属性在屏幕上的显示为视图,亦即这之间的关系是一个微型MVC模式
- 在动画过渡过程中,需要记录过渡过程当前进度的属性值,该值被存储在一个叫做呈现图层的独立图层当中。
- 呈现图层可以通过-presentationgLayer方法访问;
- 可以通过呈现图层的值来获取当前屏幕上真正显示出来的值。
- 呈现图层可以通过-modelLayer返回正在呈现所依赖的CALayer,通常在一个图层上调用-modelLayer会返回-self
- 呈现图层一般情况下不需要管理,但在这两种情况下呈现图层变得很有用:
- 1.同步动画
- 2.处理用户交互(可以使用-hitTest:方法来判断指定图层是否被触摸,这时候对呈现图层而不是模型图层调用-hitTest:会显得更有意义,因为呈现图层代表了用户当前看到的图层位置,而不是当前动画结束之后的位置)
- 情况2的示范例子:(点击屏幕上的任意位置将会让图层平移到那里。点击图层本身可以随机改变它的颜色。通过对呈现图层调用-hitTest:来判断是否被点击,)
- self.colorLayer = [CALayer layer];
- self.colorLayer.frame = CGRectMake(0, 0, 100, 100);
- self.colorLayer.position = CGPointMake(self.view.bounds.size.width / 2, self.view.bounds.size.height / 2);
- self.colorLayer.backgroundColor = [UIColor redColor].CGColor;
- [self.view.layer addSublayer:self.colorLayer];
- - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
- //get the touch point
- CGPoint point = [[touches anyObject] locationInView:self.view];
- //check if we've tapped the moving layer
- if ([self.colorLayer.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;
- self.colorLayer.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];
- self.colorLayer.position = point;
- [CATransaction commit];
- }
- }
- /*
- 如果修改代码让-hitTest:直接作用于colorLayer而不是呈现图层,会发现当图层移动的时候它并不能正确显示。
- 这时候就需要点击图层将要移动到的位置而不是图层本身来响应点击(这就是为什么用呈现图层来响应交互的原因)。
- */