一、Core Animation
提供一种简单的、声明式程序模型使得在不需要使用OpenGL或者OpenGL ES框架的情况下就可以很容易地创建高性能、基于GPU的动画效果。
Core Animation包含两类对象:
-
Layers。图层对象由CALayer类定义,并用于管理屏幕内可视化内容的元素。这里所说的内容一般都是图片或者Bezier路径,不过图层本身具有可被设置的可视化特征,比如背景色、透明度和角半径。图层定义了自身的几何属性,bounds和position,可以将这些原色组合到图层的层级结构中用于创建更复杂的界面。
- CATextLayer:用于渲染贴图内容。
- CAShaperLayer:用来渲染Bezier路径。
-
Animations。动画对象是抽象类CAAnimation的实例,定义所有动画类型所共有的一些核心动画行为。该框架定义了CAAnimation的许多具体子类,最常用的就是CABasicAnimation和CAKeyFrameAnimation。将动画状态变为单独的图层熟悉感,一边创建简单和复杂的动画效果。
- CABasicAnimation:创建简单的单关键帧动画,意味着在一段时间内将属性状态以动画方式由一种状态变为另一种状态。比如动态调整图层尺寸、位置、背景色。
- CAKeyFrameAnimation:实现更高级功能,对动画中的关键帧有更多的控制。
创建一个绕z轴旋转的动画
CALayer *parentLayer = self.view.layer;
UIImage *image = [UIImage imageNamed:@"lavf_cover"];
CALayer *imageLayer = [CALayer layer];
// 设置layer内容
imageLayer.contents = (id)image.CGImage;
imageLayer.contentsScale = [UIScreen mainScreen].scale;
CGFloat midX = CGRectGetMidX(parentLayer.bounds);
CGFloat midY = CGRectGetMidY(parentLayer.bounds);
imageLayer.bounds = CGRectMake(0, 0, image.size.width, image.size.height);
imageLayer.position = CGPointMake(midX, midY);
[parentLayer addSublayer:imageLayer];
//z轴旋转动画
CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
rotationAnimation.toValue = @(2 * M_PI);
rotationAnimation.duration = 3.f;
rotationAnimation.repeatCount = HUGE_VAL;
[imageLayer addAnimation:rotationAnimation forKey:@"r"];
二、在AVFoundation中使用Core Animation
AVFoundation使用CoreAnimation,动画需要紧密的与视频时间轴绑定,不能在根据主机时间确定时间,这是两种不同的执行时间模式。
1、AVSynchronizedLayer
AVSynchronizedLayer用于跟定定的AVPlayerItem实例同步时间。这个图层本身不展示任何内容,仅用来与图层子树协同时间。
在继承关系中附属于该图层的动画都可以从激活的AVPlayerItem实例中获取相应的执行时间。
通常使用AVSynchronizedLayer会将其整合到播放器视图的图层继承关系中,同步图层直接呈现在视图图层之上。
不只是这一个用法,还有其他用法。
2、使用AVVideoCompositionCoreAnimationTool导出
将CoreAnimation图层和动画整合到导出视频中,需要使用AVVideoCompositionAnimationTool类。AVVideoComposition使用这个类将Core Animation效果作为食品组合的后期处理阶段纳入。
它可以将组合视频帧至于视频图层中,并在之后渲染包含自定义底价效果的动画图层来生成最终的视频帧。
问题:
- CoreAnimation框架的默认行为是执行动画并在动画行为完成之后进行处理。通常这一行为就是我们希望在实时案例中使用,因为时间一旦过去就没法返回。对于视频动画需要设置动画的removedOnCompletion属性为NO来禁用这一行为。如果不设置,动画效果就是一次性的,如果用户重新播放视频或在时间轴上移动进度条也不会再次看到动画。
- 动画的时间,beginTime设置为0.0不会看到动画效果。如果希望在影片开头加入动画,将动画的beginTime设置为AVCoreAnimationBeginTimeAtZero常量。
三、添加动画标题
在CoreAnimation中使用AVComposition的一个挑战就是协调不同的概念和时间模型。在使用AVComposition时,考虑的是轨道以及CMTime和CMTimeRange值。
CoreAnimation没有轨道的概念并使用浮点数来表示时间。
简单场景中可以使用CoreAnimation自己的概念,不过当需要创建一个复杂案例时,最好在两个框架之间定义一个通用的抽象概念来创建组合资源和动画时具有标准化的模型。
创建动画图层:
#import "THTitleItem.h"
#import "THConstants.h"
@interface THTitleItem ()
@property (copy, nonatomic) NSString *text;
@property (strong, nonatomic) UIImage *image;
@property (nonatomic) CGRect bounds;
@end
@implementation THTitleItem
+ (instancetype)titleItemWithText:(NSString *)text image:(UIImage *)image {
return [[self alloc] initWithText:text image:image];
}
- (instancetype)initWithText:(NSString *)text image:(UIImage *)image {
self = [super init];
if (self) {
// Listing 12.2
_text = text;
_image = image;
_bounds = TH720pVideoRect;
//
}
return self;
}
- (CALayer *)buildLayer {
// 创建一个曾对象,父图层,包含相应的图片、文本图层和尺寸
CALayer *parentLayer = [CALayer layer];
parentLayer.frame = self.bounds;
//设置 opacity为0,不可见,只有动态调整属性值控制可见性
parentLayer.opacity = 0.0f;
CALayer *imageLayer = [self makeImageLayer];
[parentLayer addSublayer:imageLayer];
CALayer *textLayer = [self makeTextLayer];
[parentLayer addSublayer:textLayer];
//创建动画
CAAnimation *fadeInFadeOutAnimation = [self makeFadeInFadeOutAnimation];
[parentLayer addAnimation:fadeInFadeOutAnimation forKey:@"fadeAnimation"];
if (self.animateImage) {
//应用一个3D动画效果绕y轴旋转。要实现这个效果需要使用父图层的sublayerTransform属性设置一个透视变化
parentLayer.sublayerTransform = THMakePerspectiveTransform(1000);
//创建一个CAAnimation来应用旋转动画并计算一个偏移量,用于创建弹出动画。
CAAnimation *spinAnimation = [self make3DSpinAnimation];
//之后旋转动画的beginTime和duration,减去半秒,就在旋转动作最后半秒加入一个弹出动画。
NSTimeInterval offset = spinAnimation.beginTime + spinAnimation.duration -0.5f;
CAAnimation *popAnimation = [self makePopAnimationWithTimingOffset:offset];
[imageLayer addAnimation:spinAnimation forKey:nil];
[imageLayer addAnimation:popAnimation forKey:nil];
}
return parentLayer;
}
- (CALayer *)makeImageLayer {
// 创建图片层
CGSize imageSize = self.image.size;
CALayer *layer = [CALayer layer];
// CoreAnimation只能处理CoreGraphics类型,获取UIImage对象的基CGimageRef
layer.contents = (id)self.image.CGImage;
// 图片动态显示时边缘应用一个抗锯齿效果。
layer.allowsEdgeAntialiasing = YES;
layer.bounds = CGRectMake(0, 0, imageSize.width, imageSize.height);
layer.position = CGPointMake(CGRectGetMidX(self.bounds)-20.0f, 270.0f);
return layer;
}
- (CALayer *)makeTextLayer {
CGFloat fontSize = self.useLargeFont ? 64:54;
UIFont *font = [UIFont fontWithName:@"GillSans-Bold" size:fontSize];
NSDictionary *attrs = @{
NSFontAttributeName:font,
NSForegroundColorAttributeName:(id)[UIColor whiteColor].CGColor
};
NSAttributedString *string = [[NSAttributedString alloc] initWithString:self.text attributes:attrs];
CGSize textSize = [self.text sizeWithAttributes:attrs];
CATextLayer *layer = [CATextLayer layer];
layer.string = string;
layer.bounds = CGRectMake(0, 0, textSize.width, textSize.height);
layer.position = CGPointMake(CGRectGetMidX(self.bounds), 470);
layer.backgroundColor = [UIColor clearColor].CGColor;
return layer;
return nil;
}
- (CAAnimation *)makeFadeInFadeOutAnimation {
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
animation.values = @[@0.0f,@1.0f,@1.0f,@0.0f];
animation.keyTimes = @[@0.0f,@0.2f,@0.8f,@1.0f];
// 根据视频时间确定动画时间。
animation.beginTime = CMTimeGetSeconds(self.startTimeInTimeline);
animation.duration = CMTimeGetSeconds(self.timeRange.duration);
// 设置为NO,动画就不会在执行之后被移除。
animation.removedOnCompletion = NO;
return animation;
}
- (CAAnimation *)make3DSpinAnimation {
// 为图片变形添加动画效果,绕y轴旋转
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];
// 图片逆时针方向绕y轴旋转两圈。
animation.toValue = @((4*M_PI) * -1);
// 淡入淡出的方式计算beginTime和duration
animation.beginTime = CMTimeGetSeconds(self.startTimeInTimeline) + 0.2;
animation.duration = CMTimeGetSeconds(self.timeRange.duration) * 0.4;
// 为动画应用一个时间函数,默认情况下动画会使用线性曲线,略显僵硬
// 使用缓入缓出曲线。
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
return animation;
}
- (CAAnimation *)makePopAnimationWithTimingOffset:(NSTimeInterval)offset {
// 弹出动画,做一个缩放效果。
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
animation.toValue = @1.3f;
//作为前一个动画的一部分,设置beginTime为传递给方法的偏移量。
animation.beginTime = offset;
animation.duration = 0.3f;
// 动画对象的自动回溯,可以反向运行动画,使他返回到初始状态。
animation.autoreverses = YES;
animation.removedOnCompletion = NO;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
return animation;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused"
static CATransform3D THMakePerspectiveTransform(CGFloat eyePosition) {
CATransform3D transform = CATransform3DIdentity;
transform.m34 = -1.0 / eyePosition;
return transform;
}
#pragma clang diagnostic pop
@end