经过之前学习,对于Core Animation除了动画外的特性有了一些了解。本篇开始,涉及到了框架最主要特性动画的相关知识
隐式动画
事务
-
Core Animation基于一个假设构建,屏幕上的任何东西都可以(或可能)做动画。动画不需要手动打开,但是要明确关闭,否则会一直存在
这里会有一个问题,为什么更改UIView的属性,不会产生任何动画效果,若有需要必须用animation块包裹呢
-
对于CALayer属性更改不需要特意开启动画效果,例如创建一个色块,然后随机改变它的颜色,你会发现图层的颜色是平滑过渡的,而不是跳变,这个过程的持续时间是0.25s
因为我们并未指定特定的动画类型即可实现动画效果,所以叫隐式动画。隐式动画执行时间、执行内容等由CATransaction类进行管理,这个类不能实例化,但可以用
+begin
和+commit
控制入栈和出栈 -
每一次Runloop周期,Core Animation都会开始一次新的CATransaction,即使不显示使用
+begin
入栈,在任何一次的Runloop循环中,属性的改变都会被集中起来,做一次animationDuration
时间的动画注意:若使用
+setAnimationDuration:
方法来修改时间,最好压入一个新的Transaction,再修改时间,因为在当前事务的时间可能会导致同一时刻别的动画(屏幕旋转)时长被修改
回到之前的问题,UIView默认关闭了隐式动画,但提供了+animateWithDuration:animations:
事务块,这和我们对新建事务出入栈的写法是不是类似呢。其实他们是在做一样的事 UIView在动画块结束时允许提供一个完成动作,它的实现原理,其实就是使用了事务的
+setCompletionBlock:
方法
图层行为
-
首先明确一个概念,我们把改变属性时CALayer自动应用的动画称作行为。我们知道UIView对于视图属性的修改,是即时响应的,也就是说UIKit禁用了关联图层的隐式动画,要了解UIKit怎么禁用的需要知道隐式动画的实现,实质上是以下几个步骤
- 图层检查CALayerDelegate协议是否实现了
-actionForLayer:forKey
方法,如果设置了则直接返回结果 - 如果没有实现协议或是
-actionForLayer:forKey
方法,图层继续检查包含属性名称对应行为
的映射字典actions
- 如果
actions
字典没有包含对应的行为
,图层会接着在它的style
字典搜索属性名 - 如果
style
字典也找不到对应的行为
,那么图层将会直接调用定义了每个属性的标准行为
方法-defaultActionForKey:
- 图层检查CALayerDelegate协议是否实现了
通过以上几个Core Animation响应行为的流程,我们可以发现,隐式动画实际上是通过框架定义的标准行为方法
-defaultActionForKey:
返回实现的UIView对实现了对应关联图层的CALayerDelegate协议,如果属性变化不在动画块中,则UIView对所有图层行为返回了nil
禁用隐式动画还有一个方法,是使用事务类CATransacition的
+setDisableActions:
方法
总结:
- UIView关联的图层禁用了隐式动画,对这种图层做动画的唯一办法就是使用UIView的动画函数(而不是依赖CATransaction),或者继承UIView,并覆盖
-actionForLayer:forKey:
方法,或者直接创建一个显式动画 - 对于单独存在的图层,我们可以通过实现图层的
-actionForLayer:forKey:
委托方法,或者提供一个actions
字典来控制隐式动画
视觉呈现模型
-
有经验的开发者可能接触过类似MVVM模型的Vue框架,当你在VM更改了M属性后,视图层会立即回应你的修改。回到CALayer,它的属性行为其实略微有些奇怪,当你改变一个图层的属性时,视图上并没有立即生效(可以自己打一个断点实验),而是一段时间后,通过渐变更新。
通过之前的学习,我们现在可以容易的理解CALayer对属性渲染的流程,在更改属性后,runloop的下一个周期,会触发新的事务CATransaction,并查找这个属性的动画行为,如果未查到指定动画行为,则通过
-defaultActionForKey:
方法执行默认行为 其实,这就是一个微型的MVC模型,Core Animation扮演了一个控制器的角色,负责图层行为和事务设置去不断更新视图
-
在iOS中,屏幕每秒钟重绘60次(60fps)。如果动画时长比60分之一秒要长,Core Animation就需要在设置一次新值和新值生效之间,对屏幕上的图层进行重新组织。这意味着CALayer除了“真实”值(就是设置的值)之外,必须要知道当前显示在屏幕上的属性值的记录。
presentationLayer
记录了呈现图层的外观,可以通过这个属性获取屏幕上显示的真实值多数情况,我们不用访问呈现图层,但例如如果你想要知道,用户的触摸行为是否作用在运动中的图层,这时呈现图层会很有用
书中有一个例子,有一个矩形图层,当我们点击图层时改变颜色,当点击图层外侧时移动图层
- (void)viewDidLoad{
[super viewDidLoad];
//create a red layer
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];
}
}
显式动画
CAAnimation是所有动画类的抽象父类,他提供了动画类的工厂方法,以及动画类所需协议。具体的动画行为我们需要实例化他的子类
- CAPropertyAnimation
- CABasicAnimation
- CAKeyframeAnimation
- CAAnimationGroup
- CATransition
CAPropertyAnimation - 属性动画
- 属性动画通过指定CALayer的可动画属性名称赋值给
keyPath
属性,并设置相应的初始值和结束值,以达到动画效果。我们不用关心动画中的渲染实现,《熟练的艺术家》会帮我们完成动画流程渲染 -
additive
如果这个属性为YES,则动画所指定值以添加的方式计算并得到一个新的值,仿射变化也会适用 -
cumulative
下一次动画是否接着上一次动画,默认为NO
CABasicAnimation - 基本动画
- 基本动画的实现非常简单,他定义三个状态值属性,控制动画前和动画后CALayer
keyPath
属性的状态- fromValue:keyPath相应属性的初始值
- toValue:keyPath相应属性的结束值
- byValue:keyPath相应属性的相对值
- 下面代码描述了使用基本动画改变图层背景色
- (IBAction)changeColor
{
//create a new random color
CGFloat red = arc4random() / (CGFloat)INT_MAX;
CGFloat green = arc4random() / (CGFloat)INT_MAX;
CGFloat blue = arc4random() / (CGFloat)INT_MAX;
UIColor *color = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
//create a basic animation
CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"backgroundColor";
animation.toValue = (__bridge id)color.CGColor;
animation.delegate = self;
//apply animation to layer
[self.colorLayer addAnimation:animation forKey:nil];
}
- 上述代码,在
fillMode=kCAFillModeForwards
及removedOnComletion=NO
时,图层会保持动画执行后的状态,但是不要被视图渲染结果所欺骗,图层的属性值还是动画执行前的初始值,所以,我们在代码中还要实现CAAnimationDelegate
的代理方法,更改图层的属性值
- (void)animationDidStop:(CABasicAnimation *)anim finished:(BOOL)flag
{
//set the backgroundColor property to match animation toValue
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.colorLayer.backgroundColor = (__bridge CGColorRef)anim.toValue;
[CATransaction commit];
}
这里我们要禁用当前隐式动画事务,不然赋值最终值还要再被隐式动画再渲染一遍
- 不得不承认,显示地给图层添加
CABasicAnimation
动画,相较于直接添加隐式动画,只能说费力不讨好
CAKeyframeAnimation - 关键帧动画
- 关键帧动画依然作用于CALayer单一的一个属性,但他不限制于设置一个起始和结束的值,而是可以根据一连串随意的值来做动画。你只需要提供显著表现的帧值,Core Animation会在每帧之间平滑插入过度表现
- 一段简单的代码,表现了图层背景色在指定一系列颜色的变化
- (IBAction)changeColor
{
//create a keyframe animation
CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
animation.keyPath = @"backgroundColor";
animation.duration = 2.0;
animation.values = @[
(__bridge id)[UIColor blueColor].CGColor,
(__bridge id)[UIColor redColor].CGColor,
(__bridge id)[UIColor greenColor].CGColor,
(__bridge id)[UIColor blueColor].CGColor ];
//apply animation to layer
[self.colorLayer addAnimation:animation forKey:nil];
}
- 但用数组来描述动画的行为并不直观,所以关键帧动画有另外一种方式指定动画,就是使用
path
属性,它是CGPath类型,我们可以使用Core Graphics来绘制运动轨迹,但使用UIBezierPath类来绘制动画会更加简单
- (void)viewDidLoad
{
[super viewDidLoad];
//create a path
...
//create the keyframe animation
CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
animation.keyPath = @"position";
animation.duration = 4.0;
animation.path = bezierPath.CGPath;
animation.rotationMode = kCAAnimationRotateAuto;
[shipLayer addAnimation:animation forKey:nil];
}
注意这里指定了
rotationMode
旋转模板为自动,这样图层会根据曲线切线自动旋转
CAAnimationGroup - 动画组
- 属性动画仅作用于单一属性的变化,CAAnimationGroup可以将这些动画组合在一起,变成复合动画
- 可以使用
animations
属性,数组内的所有动画对象都是并发进行的 - 可以通过设置数组内动画对象的
beginTime
属性改变开始时间
CATransition - 过渡动画
- 属性动画只能对图层的可动画属性起作用,所以如果要动态改变(如图片)不能动画的属性,或者移除添加图层,属性动画将会失效
- 过渡动画可以影响不能动画的属性,他首先展示之前的图层外观,然后通过指定的交换方式(type),平滑过渡到新的外观
- 过渡动画通过
type
来定以动画效果,他是一个NSString类型- fade - kCATransitionFade 平滑淡化过渡(不支持过渡方向)
- moveIn - kCATransitionMoveIn 新视图移到旧视图上面
- push - kCATransitionPush 新视图把旧视图推出
- reveal - kCATransitionReveal 将旧视图移开显示下面的旧视图
- cube 立方体翻滚效果
- oglFlip 上下左右翻转效果
- suckEffect 收缩效果,如一块布被抽走(不支持过渡方向)
- rippleEffect 滴水效果(不支持过渡方向)
- pageCurl 向上翻页效果
- pageUnCurl 向下翻页效果
- cameraIrisHollowOpen 相机镜头打开效果(不支持过渡方向)
- cameraIrisHollowClose 相机镜头关上效果(不支持过渡方向)
-
subtype
属性指定动画的方向- kCATransitionFromRight
- kCATransitionFromLeft
- kCATransitionFromTop
- kCATransitionFromBottom
-
startProgress
动画起点(在整体动画的百分比) -
endProgress
动画终点(在整体动画的百分比) - 一个实例,切换UITabBarController标签时淡入淡出动画
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
//set up crossfade transition
CATransition *transition = [CATransition animation];
transition.type = kCATransitionFade;
//apply transition to tab bar controller's view
[self.tabBarController.view.layer addAnimation:transition forKey:nil];
}