MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Objective-C动画实现:Core Animation基础

2022-01-162.1k 阅读

Core Animation 简介

Core Animation 是一个在 iOS 和 macOS 开发中用于创建高性能动画和视觉效果的框架。尽管它的名字中有 “Animation”,但它实际上并不直接处理动画的时间控制或逻辑。相反,Core Animation 主要负责在屏幕上高效地合成和显示图形内容,并提供了一种基于层(layer)的模型来管理和操作这些内容。

在 Objective-C 开发中,Core Animation 是构建流畅、吸引人的用户界面的重要工具。它允许开发者创建各种类型的动画,从简单的视图过渡到复杂的 3D 动画效果。Core Animation 的底层实现是基于 GPU 加速的,这使得它能够在不影响应用程序性能的情况下处理大量的图形数据。

CALayer 基础

什么是 CALayer

CALayer 是 Core Animation 框架的核心类之一。它代表了屏幕上的一个矩形区域,用于显示图形内容。每个 UIView 都有一个关联的 CALayer 实例,负责实际的绘制和动画。然而,CALayer 比 UIView 更加轻量级,并且可以独立于 UIView 使用,这使得它在创建复杂的动画和图形效果时非常有用。

创建和配置 CALayer

在 Objective-C 中,可以通过以下方式创建一个 CALayer 实例:

#import <QuartzCore/QuartzCore.h>

// 创建一个新的 CALayer
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(100, 100, 200, 200);
layer.backgroundColor = [UIColor redColor].CGColor;

在上述代码中,首先导入了 QuartzCore/QuartzCore.h 头文件,这是使用 Core Animation 框架所必需的。然后通过 [CALayer layer] 方法创建了一个新的 CALayer 实例,并设置了它的 frame 属性来定义其在父层中的位置和大小,backgroundColor 属性设置为红色。

图层层级

CALayer 支持一个层级结构,类似于 UIView 的视图层级。一个 CALayer 可以有多个子层,并且它本身也可以是另一个 CALayer 的子层。通过这种层级结构,可以创建复杂的图形布局。

// 创建一个父层
CALayer *parentLayer = [CALayer layer];
parentLayer.frame = CGRectMake(50, 50, 300, 300);
parentLayer.backgroundColor = [UIColor grayColor].CGColor;

// 创建一个子层
CALayer *childLayer = [CALayer layer];
childLayer.frame = CGRectMake(50, 50, 200, 200);
childLayer.backgroundColor = [UIColor greenColor].CGColor;

// 将子层添加到父层
[parentLayer addSublayer:childLayer];

在这段代码中,首先创建了一个灰色的父层 parentLayer,然后创建了一个绿色的子层 childLayer,并将 childLayer 添加到 parentLayer 中作为子层。这样就形成了一个简单的图层层级结构。

Core Animation 动画类型

隐式动画

在 Core Animation 中,许多属性的更改会触发隐式动画。例如,当你更改 CALayer 的 positionboundsopacity 等属性时,Core Animation 会自动为这些更改添加动画效果,而不需要你显式地创建动画对象。

// 创建一个 CALayer
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(100, 100, 100, 100);
layer.backgroundColor = [UIColor blueColor].CGColor;

// 将 layer 添加到视图的 layer 中
[self.view.layer addSublayer:layer];

// 延迟 2 秒后更改 layer 的位置
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    layer.position = CGPointMake(300, 300);
});

在上述代码中,创建了一个蓝色的 CALayer 并添加到视图的 layer 中。然后使用 dispatch_after 延迟 2 秒后更改 layerposition 属性。由于 Core Animation 的隐式动画特性,这个位置的更改会以动画的形式呈现。

显式动画

显式动画允许你对动画进行更精细的控制,包括动画的持续时间、缓动效果、重复次数等。要创建显式动画,需要使用 CAAnimation 及其子类,如 CABasicAnimationCAKeyframeAnimation 等。

CABasicAnimation

CABasicAnimation 用于创建基于单个起始值和结束值的简单动画。例如,以下代码创建了一个使 CALayer 透明度从 1 变为 0 的动画:

// 创建一个 CALayer
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(100, 100, 100, 100);
layer.backgroundColor = [UIColor purpleColor].CGColor;
[self.view.layer addSublayer:layer];

// 创建一个 CABasicAnimation
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
animation.fromValue = @(1.0);
animation.toValue = @(0.0);
animation.duration = 2.0;

// 将动画添加到 layer 上
[layer addAnimation:animation forKey:@"fadeOutAnimation"];

在这段代码中,首先创建了一个紫色的 CALayer 并添加到视图的 layer 中。然后创建了一个 CABasicAnimation,指定 keyPathopacity,表示要对透明度属性进行动画。设置 fromValue 为 1.0(完全不透明),toValue 为 0.0(完全透明),duration 为 2 秒。最后将动画添加到 layer 上,动画就会开始执行。

CAKeyframeAnimation

CAKeyframeAnimation 允许你定义一系列关键帧来创建更复杂的动画。每个关键帧定义了动画在某个时间点的属性值。例如,以下代码创建了一个使 CALayer 沿着一条自定义路径移动的动画:

// 创建一个 CALayer
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(50, 50, 50, 50);
layer.backgroundColor = [UIColor orangeColor].CGColor;
[self.view.layer addSublayer:layer];

// 创建一个 CGPath 作为动画路径
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(100, 100)];
[path addCurveToPoint:CGPointMake(300, 300) controlPoint1:CGPointMake(150, 200) controlPoint2:CGPointMake(250, 200)];

// 创建一个 CAKeyframeAnimation
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
animation.path = path.CGPath;
animation.duration = 3.0;

// 将动画添加到 layer 上
[layer addAnimation:animation forKey:@"moveAlongPathAnimation"];

在上述代码中,首先创建了一个橙色的 CALayer 并添加到视图的 layer 中。然后使用 UIBezierPath 创建了一条曲线作为动画路径。接着创建了一个 CAKeyframeAnimation,指定 keyPathposition,表示要对位置属性进行动画,并将 path 设置为刚才创建的路径,duration 为 3 秒。最后将动画添加到 layer 上,CALayer 就会沿着指定的路径移动。

动画的时间控制和缓动效果

动画持续时间

在前面的示例中,已经通过 duration 属性设置了动画的持续时间。例如,在 CABasicAnimationCAKeyframeAnimation 中都可以这样设置:

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
animation.duration = 2.0; // 动画持续 2 秒

这个 duration 属性决定了动画从开始到结束所需要的时间,单位是秒。

延迟时间

可以通过 beginTime 属性设置动画的延迟时间。beginTime 是相对于当前时间的一个时间偏移量。例如,以下代码使动画延迟 1 秒开始:

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
animation.beginTime = CACurrentMediaTime() + 1.0; // 延迟 1 秒开始

这里使用 CACurrentMediaTime() 获取当前时间,然后加上 1.0 表示延迟 1 秒。

缓动效果

Core Animation 提供了多种缓动效果来控制动画的速度变化。常见的缓动效果包括线性、渐入、渐出、渐入渐出等。可以通过 timingFunction 属性来设置缓动效果。例如,以下代码设置动画为渐入效果:

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];

这里使用 [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn] 创建了一个渐入效果的 CAMediaTimingFunction 实例,并将其设置为动画的 timingFunction。其他可用的缓动效果名称包括 kCAMediaTimingFunctionLinear(线性)、kCAMediaTimingFunctionEaseOut(渐出)、kCAMediaTimingFunctionEaseInEaseOut(渐入渐出)等。

动画的组合和群组

动画群组

可以使用 CAAnimationGroup 类将多个动画组合在一起,使它们同时执行。例如,以下代码创建了一个同时包含位置和透明度变化的动画群组:

// 创建一个 CALayer
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(100, 100, 100, 100);
layer.backgroundColor = [UIColor brownColor].CGColor;
[self.view.layer addSublayer:layer];

// 创建位置动画
CABasicAnimation *positionAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
positionAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(300, 300)];
positionAnimation.duration = 2.0;

// 创建透明度动画
CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
opacityAnimation.toValue = @(0.0);
opacityAnimation.duration = 2.0;

// 创建动画群组
CAAnimationGroup *group = [CAAnimationGroup animation];
group.animations = @[positionAnimation, opacityAnimation];
group.duration = 2.0;

// 将动画群组添加到 layer 上
[layer addAnimation:group forKey:@"groupAnimation"];

在这段代码中,首先创建了一个棕色的 CALayer 并添加到视图的 layer 中。然后分别创建了位置动画 positionAnimation 和透明度动画 opacityAnimation。接着创建了一个 CAAnimationGroup,将两个动画添加到 animations 数组中,并设置群组的 duration 为 2 秒。最后将动画群组添加到 layer 上,两个动画就会同时执行。

动画序列

通过设置动画的 beginTime 属性,可以创建动画序列,使动画按顺序执行。例如,以下代码先执行位置动画,2 秒后再执行透明度动画:

// 创建一个 CALayer
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(100, 100, 100, 100);
layer.backgroundColor = [UIColor cyanColor].CGColor;
[self.view.layer addSublayer:layer];

// 创建位置动画
CABasicAnimation *positionAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
positionAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(300, 300)];
positionAnimation.duration = 2.0;

// 创建透明度动画
CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
opacityAnimation.toValue = @(0.0);
opacityAnimation.duration = 2.0;
opacityAnimation.beginTime = positionAnimation.beginTime + positionAnimation.duration;

// 将位置动画添加到 layer 上
[layer addAnimation:positionAnimation forKey:@"positionAnimation"];
// 将透明度动画添加到 layer 上
[layer addAnimation:opacityAnimation forKey:@"opacityAnimation"];

在上述代码中,首先创建了一个青色的 CALayer 并添加到视图的 layer 中。然后创建了位置动画 positionAnimation 和透明度动画 opacityAnimation。通过设置 opacityAnimationbeginTimepositionAnimationbeginTime 加上 positionAnimationduration,实现了先执行位置动画,2 秒后再执行透明度动画。

3D 动画

3D 变换基础

Core Animation 支持在 CALayer 上应用 3D 变换,通过修改 transform 属性来实现。CATransform3D 结构体用于表示 3D 变换。例如,以下代码将一个 CALayer 绕 X 轴旋转 45 度:

// 创建一个 CALayer
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(100, 100, 200, 200);
layer.backgroundColor = [UIColor magentaColor].CGColor;
[self.view.layer addSublayer:layer];

// 创建一个 3D 变换
CATransform3D transform = CATransform3DMakeRotation(M_PI_4, 1, 0, 0);
layer.transform = transform;

在这段代码中,首先创建了一个品红色的 CALayer 并添加到视图的 layer 中。然后使用 CATransform3DMakeRotation 函数创建了一个绕 X 轴旋转 45 度(M_PI_4 表示 π/4 弧度)的 3D 变换,并将其应用到 layertransform 属性上。

3D 动画实现

可以通过对 transform 属性进行动画来创建 3D 动画。例如,以下代码创建了一个使 CALayer 绕 Y 轴旋转 360 度的动画:

// 创建一个 CALayer
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(100, 100, 200, 200);
layer.backgroundColor = [UIColor yellowColor].CGColor;
[self.view.layer addSublayer:layer];

// 创建一个 3D 变换动画
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];
animation.toValue = @(M_PI * 2);
animation.duration = 3.0;
animation.repeatCount = HUGE_VALF;

// 将动画添加到 layer 上
[layer addAnimation:animation forKey:@"3DRotationAnimation"];

在上述代码中,首先创建了一个黄色的 CALayer 并添加到视图的 layer 中。然后创建了一个 CABasicAnimation,指定 keyPathtransform.rotation.y,表示对绕 Y 轴的旋转属性进行动画。设置 toValueM_PI * 2(表示旋转 360 度),duration 为 3 秒,repeatCountHUGE_VALF 表示无限重复。最后将动画添加到 layer 上,CALayer 就会绕 Y 轴持续旋转。

动画的交互和控制

动画的暂停和恢复

可以通过修改 CALayer 的 speedtimeOffset 属性来暂停和恢复动画。例如,以下代码实现了暂停和恢复一个正在运行的动画:

// 创建一个 CALayer
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(100, 100, 100, 100);
layer.backgroundColor = [UIColor greenColor].CGColor;
[self.view.layer addSublayer:layer];

// 创建一个动画
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
animation.toValue = [NSValue valueWithCGPoint:CGPointMake(300, 300)];
animation.duration = 5.0;
[layer addAnimation:animation forKey:@"moveAnimation"];

// 暂停动画
- (void)pauseLayer:(CALayer*)layer {
    CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
    layer.speed = 0.0;
    layer.timeOffset = pausedTime;
}

// 恢复动画
- (void)resumeLayer:(CALayer*)layer {
    CFTimeInterval pausedTime = layer.timeOffset;
    layer.speed = 1.0;
    layer.timeOffset = 0.0;
    layer.beginTime = 0.0;
    CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
    layer.beginTime = timeSincePause;
}

在上述代码中,首先创建了一个绿色的 CALayer 并添加了一个位置移动动画。然后定义了 pauseLayer:resumeLayer: 两个方法,分别用于暂停和恢复动画。在 pauseLayer: 方法中,获取当前时间并暂停动画,记录暂停时间。在 resumeLayer: 方法中,恢复动画的速度,重置时间偏移和开始时间,并计算从暂停到恢复所经过的时间,以便动画能从暂停的位置继续执行。

动画的停止

可以通过调用 removeAnimationForKey: 方法来停止 CALayer 上指定的动画。例如:

// 停止动画
[layer removeAnimationForKey:@"moveAnimation"];

这段代码会停止 layer 上名为 moveAnimation 的动画。如果要停止所有动画,可以使用 removeAllAnimations 方法:

// 停止所有动画
[layer removeAllAnimations];

通过这些方法,可以根据用户的交互或应用程序的逻辑来灵活地控制动画的状态。

Core Animation 的性能优化

避免离屏渲染

离屏渲染是指在当前屏幕缓冲区之外的内存区域进行渲染操作,这会消耗更多的性能。某些 CALayer 的属性设置可能会导致离屏渲染,例如设置 cornerRadiusshadow 等。为了避免离屏渲染,可以尽量减少这些属性的使用,或者使用 shouldRasterize 属性并设置 rasterizationScale 来缓存渲染结果。例如:

layer.shouldRasterize = YES;
layer.rasterizationScale = [UIScreen mainScreen].scale;

这样可以将图层渲染结果缓存起来,减少重复渲染的开销。

优化动画复杂度

尽量简化动画的复杂度,避免同时执行过多复杂的动画。例如,减少关键帧动画中的关键帧数,或者避免在动画中频繁修改大量的属性。对于复杂的动画,可以考虑分阶段执行,而不是一次性执行所有的动画效果。

使用 GPU 加速

Core Animation 本身是基于 GPU 加速的,但为了充分利用 GPU 的性能,要确保图形数据的格式和处理方式适合 GPU 处理。例如,尽量使用纹理来处理图像数据,并且避免在主线程中进行大量的图形计算,将这些操作放到后台线程中执行,以确保主线程的流畅性。

通过遵循这些性能优化原则,可以在使用 Core Animation 创建动画时,保证应用程序的高性能和流畅性,为用户提供更好的体验。在实际开发中,需要根据具体的应用场景和需求,灵活运用这些优化技巧,以达到最佳的效果。

在 Objective-C 开发中,Core Animation 为开发者提供了强大而灵活的动画创建工具。通过深入理解 CALayer 的基础、各种动画类型、时间控制、动画组合以及性能优化等方面的知识,开发者能够创建出丰富多样、高性能的动画和视觉效果,提升应用程序的用户体验。无论是简单的视图过渡动画,还是复杂的 3D 动画场景,Core Animation 都能够满足开发者的需求,成为构建优秀 iOS 和 macOS 应用程序的重要组成部分。在实际项目中,需要不断实践和尝试,结合应用的具体需求,充分发挥 Core Animation 的优势,打造出令人惊艳的用户界面动画效果。同时,也要注意性能优化,确保动画的流畅性和应用程序的整体性能不受影响。随着技术的不断发展,Core Animation 也可能会有新的特性和改进,开发者需要持续关注并学习,以保持在动画开发领域的领先地位。在处理复杂的动画需求时,合理规划动画的逻辑和结构,将有助于提高开发效率和代码的可维护性。例如,可以将动画相关的代码封装成独立的类或方法,以便在不同的场景中复用。此外,与用户界面设计团队密切合作,确保动画效果与整体的设计风格相匹配,也是打造成功应用程序的关键因素之一。通过不断地积累经验和学习新的技术,开发者能够在 Core Animation 的帮助下,创造出更加精彩的应用程序动画体验。