有趣炫酷又实用的iOS代码库iOS效果iOS开发

iOS动画总结:UIKit动画和Core Animation

2017-02-15  本文已影响703人  BigDaddy_

前言

最近在尝试一个翻页的动画效果,看了一些资料后,对苹果的动画实现这部分有了一个比较清楚的了解,仅仅止于比较清楚的了解,觉得有必要写篇博客记录一下,这篇博客更多的参考了苹果官方的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优化。

上一篇下一篇

猜你喜欢

热点阅读