iOS动画总结:UIKit动画和Core Animation
前言
最近在尝试一个翻页的动画效果,看了一些资料后,对苹果的动画实现这部分有了一个比较清楚的了解,仅仅止于比较清楚的了解,觉得有必要写篇博客记录一下,这篇博客更多的参考了苹果官方的API说明,同时也谢谢一些前辈的博客,整个博客前前后后大概读了20多篇各种博客和官方文章,最后文章的定位在对苹果动画部分的大体介绍上,为的是大家都有一个整体结构在脑袋中。至于细节的部分,涉及的不多,如果都写下来,文章会很庞大,而且没有重点。所以细节的部分几乎没什么介绍。对细节及具体实现感兴趣的各位可以动动手指自己去查查,网上细节的东西一搜一大把,我就不再重复整理了。
iOS系统对动画实现的整体架构如下图所示:
123.png经常用到的就是UIKit动画和Core Animation,而这两部分最底层的实现都是基于OpenGL ES,想更详细了解动画算法以及动画具体实现过程的同学可以从先了解OpenGL ES开始。
动画分类:隐式动画和显示动画
1. 隐式动画
每个UIView都有一个layer属性,它的类型是CALayer,属于QuartzCore框架。CALayer本身并不包含在UIKit中,它不能响应事件。由于CALayer在设计之初就考虑了它的动画操作功能,CALayer很多属性在修改时都能形成动画效果,这种属性称为“隐式动画属性”。 对每个UIView的非root layer对象属性进行修改时,都会形成隐式动画。之所以叫隐式是因为我们并没有指定任何动画的类型。我们仅仅改变了一个属性,然后Core Animation来决定如何并且何时去做动画。
CALayer的隐式动画实际上是自动执行了CATransaction动画,执行一次隐式动画大概是0.25秒。UIView的动画底层也是使用CATranscation实现的。
2.显式动画
iOS显式动画有两类动画方式: UIKit 和 core animation,
2.1 UIKit
UIKit动画实质上是对CoreAnimation的封装,提供简洁的动画接口。UIView动画可以设置的动画属性有:
a、大小变化(frame)
b、拉伸变化(bounds)
c、中心位置(center)
d、旋转(transform)
e、透明度(alpha)
f、背景颜色(backgroundColor)
g、拉伸内容(contentStretch)
2.1.1 UIKit中执行动画的方法
UIKit中实现动画效果主要通过以下三个方法,大家可以根据自己的需求选择使用哪一个。
• animateWithDuration:animations:
• animateWithDuration:animations:completion:
• animateWithDuration:delay:options:animations:completion:
我们举个改变背景色的例子:
[UIView animateWithDuration:3.0f
delay:0.0f
options:(UIViewAnimationOptionRepeat | UIViewAnimationOptionAllowUserInteraction)
animations:^{
self.view.backgroundColor = [UIColor orangeColor];
} completion:^(BOOL finished){
self.view.backgroundColor = [UIColor lightGrayColor];
}];
方法中options参数可选的选项如下所示:
typedef NS_OPTIONS(NSUInteger, UIViewAnimationOptions) {
//动画属性
UIViewAnimationOptionLayoutSubviews = 1 << 0,
UIViewAnimationOptionAllowUserInteraction = 1 << 1, // turn on user interaction while animating
UIViewAnimationOptionBeginFromCurrentState = 1 << 2, // start all views from current value, not initial value
UIViewAnimationOptionRepeat = 1 << 3, // repeat animation indefinitely
UIViewAnimationOptionAutoreverse = 1 << 4, // if repeat, run animation back and forth
UIViewAnimationOptionOverrideInheritedDuration = 1 << 5, // ignore nested duration
UIViewAnimationOptionOverrideInheritedCurve = 1 << 6, // ignore nested curve
UIViewAnimationOptionAllowAnimatedContent = 1 << 7, // animate contents (applies to transitions only)
//动画线性关系
UIViewAnimationOptionShowHideTransitionViews = 1 << 8, // flip to/from hidden state instead of adding/removing
UIViewAnimationOptionOverrideInheritedOptions = 1 << 9, // do not inherit any options or animation type
UIViewAnimationOptionCurveEaseInOut = 0 << 16, // default
UIViewAnimationOptionCurveEaseIn = 1 << 16,
UIViewAnimationOptionCurveEaseOut = 2 << 16,
UIViewAnimationOptionCurveLinear = 3 << 16,
//转场动画
UIViewAnimationOptionTransitionNone = 0 << 20, // default
UIViewAnimationOptionTransitionFlipFromLeft = 1 << 20,
UIViewAnimationOptionTransitionFlipFromRight = 2 << 20,
UIViewAnimationOptionTransitionCurlUp = 3 << 20,
UIViewAnimationOptionTransitionCurlDown = 4 << 20,
UIViewAnimationOptionTransitionCrossDissolve = 5 << 20,
UIViewAnimationOptionTransitionFlipFromTop = 6 << 20,
UIViewAnimationOptionTransitionFlipFromBottom = 7 << 20,
} NS_ENUM_AVAILABLE_IOS(4_0);
2.1.2 CGAffineTransform
CGAffineTransform用于绘制2D图形的仿射变换矩阵。CGAffineTransform的深层次的原理我还不是很明白,就先不说了,等回头研究明白了再补上。
CGAffineTransform的定义如下:
struct CGAffineTransform {
CGFloat a, b, c, d;
CGFloat tx, ty;
};
typedef struct CGAffineTransform CGAffineTransform;
CGAffineTransform能完成的动画效果包括平移,缩放,旋转以及这三种效果之间的各种组合,下面简单举一个平移的例子:
[UIView animateWithDuration:0.5 animations:^{
view.transform = CGAffineTransformTranslate(v.transform,10,0);
}];
2.2 core animation:
如果你的动画效果很复杂或者UIKit不能实现,可以尝试用Core Animation和layer来实现。你可以直接用Core Animation来自定义layer的动画效果。因为view和layer密切的关系,layer的变化会直接影响到view。用Core Animation实现动画的方法,简单的说有两种,用CATransform3D和CAAnimation的子类。
2.2.1 Core Animation的动画效果
Core Animation可以实现的动画效果如下所示(由于英文水平有限,就不翻译了,直接贴了苹果官方的说明):
• The size and position of the layer
• The center point used when performing transformations
• Transformations to the layer or its sublayers in 3D space
• The addition or removal of a layer from the layer hierarchy
• The layer’s Z-order relative to other sibling layers
• The layer’s shadow
• The layer’s border (including whether the layer’s corners are rounded)
• The portion of the layer that stretches during resizing operations
• The layer’s opacity
• The clipping behavior for sublayers that lie outside the layer’s bounds
• The current contents of the layer
• The rasterization behavior of the layer
• Note: If your view hosts custom layer objects—that is, layer objects without an associated view—you must use Core Animation to animate any changes to them.
2.2.2 CAAnimation及其子类:
CAAnimation及其子类的关系如下图:
FB51D8F9-FE2A-4D85-90B9-75C4B756D49C.png上图中列出的类中我们经常用到的只有CABasicAnimation,CAAnimationGroup,CAKeyframeAnimation和CATransition。CAPropertyAnimation可以理解为是CAAnimation的一个抽象子类,只是用来扩展CAAnimation的功能以及派生其他子类的。
CAAniamtion实现了一个很重要的协议:CAMediaTiming。像duration、beginTime、endTime和repeatCount这些时间相关的属性都在这个类中。大体而言,这个协议中定义了8个属性,这些属性通过一些方式结合在一起,准确的控制着动画的时间和过程。
CABasicAnimation和CAAnimationGroup:
大部分不太复杂的动画用CABasicAnimation配合CAAnimationGroup都能实现,例如移动,缩放,旋转等等,下面举一个简单的例子:
CABasicAnimation *positionAnima = [CABasicAnimation animationWithKeyPath:@"position.y"];
positionAnima.fromValue = @(view.center.y);
positionAnima.toValue = @(view.center.y-30);
positionAnima.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
/*单独一个动画的用法*/
//[view.layer addAnimation:positionAnima forKey:@"AnimationMoveY"];
CABasicAnimation *transformAnima = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];
transformAnima.fromValue = @(0);
transformAnima.toValue = @(M_PI);
transformAnima.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
/*一组动画的用法*/
CAAnimationGroup *animaGroup = [CAAnimationGroup animation];
animaGroup.duration = 2.0f;
animaGroup.fillMode = kCAFillModeForwards;
animaGroup.removedOnCompletion = NO;
animaGroup.animations = @[positionAnima,transformAnima];
[view.layer addAnimation:animaGroup forKey:@"Animation"];
CAKeyframeAnimation:
CAKeyframeAnimation实现复杂的需要精细控制的动画的时候才会用到,经常会配合贝塞尔曲线一块实现一些精美的动画。下面只是一个非常简单的例子,只是为了简单演示一下用法:
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:view.center];
[path addCurveToPoint:CGPointMake(270, 410) controlPoint1:CGPointMake(0, ScreenHeight) controlPoint2:CGPointMake(ScreenWidth, 0)];
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
animation.path = path.CGPath;
animation.duration = 3.0f;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
[view.layer addAnimation:animation forKey:nil];
CATransition:
CATransition通过名字我们大概可以猜到,这个主要是提供过度动画的,主要为图层不同状态之间变换时提供动画效果。
引用苹果官方的一个例子:
let transitioningLayer = CATextLayer()
override func viewDidLoad() {
super.viewDidLoad()
transitioningLayer.frame = CGRect(x: 10, y: 10,
width: 320, height: 160)
view.layer.addSublayer(transitioningLayer)
// Initial "red" state
transitioningLayer.backgroundColor = UIColor.red.cgColor
transitioningLayer.string = "Red"
}
func runTransition() {
let transition = CATransition()
transition.duration = 2
transition.type = kCATransitionPush
transitioningLayer.add(transition,
forKey: "transition")
// Transition to "blue" state
transitioningLayer.backgroundColor = UIColor.blue.cgColor
transitioningLayer.string = "Blue"
}
CASpringAnimation:
iOS9才引入的动画类,用于制作类似弹簧弹跳效果的动画。例如:如果使用一个弹簧动画来改变一个layer的位置,看上去这个layer似乎被一个弹簧拉向目标。 layer离目标位置越远,朝向目标的加速度就越大。
CASpringAnimation定义了很多物理相关的属性,例如弹簧的阻尼、刚度、物体的质量等。具体都有那些属性大家可以去看CASpringAnimation的定义。
下面这个例子使用弹性动画来缩放layer的:
let springAnimation = CASpringAnimation(keyPath: "transform.scale")
springAnimation.fromValue = 0
springAnimation.toValue = 1
2.2.3 CATransform3D
CATransform3D定义了在核心动画中使用的标准变换矩阵。变换矩阵用于旋转,缩放,平移,倾斜和投影层内容。 Core Animation也提供了用于创建,组合和修改CATransform3D数据的各种API。
CATransform3D的结构如下:
struct CATransform3D
{
CGFloat m11(x缩放), m12(y切变), m13(旋转), m14();
CGFloat m21(x切变), m22(y缩放), m23(), m24();
CGFloat m31(旋转), m32( ), m33(), m34;
CGFloat m41(x平移), m42(y平移), m43(z平移), m44();
};
单独看这个定义可能有点懵,我们用个例子来解释一下这个矩阵。
CATransform3DMakeTranslation是基于CATransform3D扩展的用于平移动画的API,他的定义及注释如下所示:
/* Returns a transform that translates by '(tx, ty, tz)':
t' =
[1 0 0 0;
0 1 0 0;
0 0 1 0;
tx ty tz 1].
*/
CA_EXTERN CATransform3D CATransform3DMakeTranslation (CGFloat tx,
CGFloat ty, CGFloat tz)CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
通过注释我们就能清楚的知道,tx ty tz分别对应于m41 m42 m43,tx是来实现x方向的平移动画的, ty是来实现y方向的平移动画的, tz是来实现z方向的平移动画的.
下面来看一个具体的应用:
CALayer *layer = [CALayer layer];
doorLayer.frame = CGRectMake(0, 0, 128, 256);
doorLayer.position = CGPointMake(150 - 64, 150);
doorLayer.anchorPoint = CGPointMake(0, 0.5);
doorLayer.contents = [UIImage imageNamed: @"demo.png"].CGImage;
[self.view.layer addSublayer:layer];
//apply perspective transform
CATransform3D perspective = CATransform3DIdentity;
perspective.m34 = -1.0/500.0;
self.containerView.layer.sublayerTransform = perspective;
//apply swinging animation
CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"transform.rotation.y";
animation.toValue = @(-M_PI_2);
animation.duration = 2.0;
animation.repeatDuration = INFINITY;
animation.autoreverses = YES;
[layer addAnimation:animation forKey:nil];
需要特别说明的一点是关于结构体中的m34:
在默认情况下,系统采用正交投影,对于3D形变实际上是看不到3D效果的,在CATransform3D结构体中有一个m34便允许我们将正交投影修改为有近大远小立体效果的透视投影,其中m34 = -1.0/z,这个z为观察者与控件之间的距离。简单的说就是m34是为了实现透视效果的,透视效z越小,透视效果越明显,必须在有旋转效果的前提下,才会看到透视效果。m34必须在赋值transform之前设置才会生效
额外补充:
1.混合动画
UIKit和core animation的混合实现动画过程中,动画过程中的参数效果取决于layer所属的view。更改view拥有的layer与更改view本身相同,应用于layer属性的任何动画都会遵循当前基于view的动画块的参数。
但是对于自己创建的layers却不是这样的。自定义的layers忽略了view的动画块的参数,并改为使用默认Core Animation参数。如果要自定义创建的layers的动画参数,则必须直接使用Core Animation。 您可以从view动画块的内部或外部使用Core Animation。
下面的例子展示了同时修改视图和自定义图层的动画。 此示例中自定义了CALayer对象位于view的中心位置。 view逆时针旋转,manLayer顺时针旋转。
下面的例子主要用于演示如何混合视图和图层动画。 这种类型的混合不应用于需要精确计时的情况。
[UIView animateWithDuration:1.0
delay:0.0
options: UIViewAnimationOptionCurveLinear
animations:^{
// Animate the first half of the view rotation.
CGAffineTransform xform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(-180));
backingView.transform = xform;
// Rotate the embedded CALayer in the opposite direction.
CABasicAnimation* layerAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
layerAnimation.duration = 2.0;
layerAnimation.beginTime = 0; //CACurrentMediaTime() + 1;
layerAnimation.valueFunction = [CAValueFunction functionWithName:kCAValueFunctionRotateZ];
layerAnimation.timingFunction = [CAMediaTimingFunction
functionWithName:kCAMediaTimingFunctionLinear];
layerAnimation.fromValue = [NSNumber numberWithFloat:0.0];
layerAnimation.toValue = [NSNumber numberWithFloat:DEGREES_TO_RADIANS(360.0)];
layerAnimation.byValue = [NSNumber numberWithFloat:DEGREES_TO_RADIANS(180.0)];
[manLayer addAnimation:layerAnimation forKey:@"layerAnimation"];
}
completion:^(BOOL finished){
// Now do the second half of the view rotation.
[UIView animateWithDuration:1.0
delay: 0.0
options: UIViewAnimationOptionCurveLinear
animations:^{
CGAffineTransform xform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(-359));
backingView.transform = xform;
}
completion:^(BOOL finished){
backingView.transform = CGAffineTransformIdentity;
}];
}];
2.动画重复及反向动画
对于重复动画,动画的每个完整循环都涉及从原始值到新值的动画,并再次返回。 如果希望动画以新值结束,则将repeatCount设置为x.5的值。 如果你不包括这个.5,你的动画将以回到原始值结束,这可能达不到你想要的效果。
3.Nesting Animation Blocks-嵌套动画块
您可以通过嵌套其他动画块,为动画块的某些部分分配不同的时序和配置选项。 顾名思义,嵌套动画块是在现有动画块内创建新的动画块。 嵌套动画与任何父动画同时启动,但运行(大多数情况下)具有自己的配置选项。 默认情况下,嵌套动画会继承父级的持续时间和动画曲线,如果需要重新设置,只需要在嵌套动画块中使用UIViewAnimationOptionOverrideInheritedCurve和UIViewAnimationOptionOverrideInheritedDuration键,这两个值允许为第二个动画设置自己的动画曲线和持续时间值。
请看下面的例子,两个视图被淡化为完全透明,但是anotherView对象的透明度在最终隐藏之前来回更改几次。
[UIView animateWithDuration:1.0
delay: 1.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
aView.alpha = 0.0;
// Create a nested animation that has a different
// duration, timing curve, and configuration.
[UIView animateWithDuration:0.2
delay:0.0
options: UIViewAnimationOptionOverrideInheritedCurve |
UIViewAnimationOptionCurveLinear |
UIViewAnimationOptionOverrideInheritedDuration |
UIViewAnimationOptionRepeat |
UIViewAnimationOptionAutoreverse
animations:^{
[UIView setAnimationRepeatCount:2.5];
anotherView.alpha = 0.0;
}
completion:nil];
}
completion:nil];
UIView 视图切换的转场动画
UIView视图的动画功能,可以很容易实现视图之间的动画过渡,在更新或切换视图时产生流畅的动画效果,从而达到改善用户体验。UIView转场可以实现的动画效果如下所示:
UIViewAnimationOptionTransitionNone //无转场动画
UIViewAnimationOptionTransitionFlipFromLeft //转场从左翻转
UIViewAnimationOptionTransitionFlipFromRight //转场从右翻转
UIViewAnimationOptionTransitionCurlUp //上卷转场
UIViewAnimationOptionTransitionCurlDown //下卷转场
UIViewAnimationOptionTransitionCrossDissolve //转场交叉消失
UIViewAnimationOptionTransitionFlipFromTop //转场从上翻转
UIViewAnimationOptionTransitionFlipFromBottom //转场从下翻转
下面看一个简单的例子:
[UIView transitionWithView:containerView
duration:0.2
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{ [fromView removeFromSuperview]; [containerView addSubview:toView]; }
completion:NULL];
CATransform3D和CGAffineTransform的不同
它们用于不同的情况。 CGAffineTransform用于NSViews,UIViews和其他2-D核心图形元素进行2-D操作。CATransform3D是一个Core animation结构,可以做更复杂的CALayers 3-D操作。 CATransform3D具有与OpenGL模型视图矩阵相同的内部结构。 这种内部结构的相似性,加上一些不错的帮助函数,可以让你做一些整洁的OpenGL优化。