iOS开发基础33-核心动画(一)CALayer 与 Core Animation 深度解析

CALayer 与 Core Animation 深度解析:图层属性、隐式动画、基本动画、关键帧动画、动画组与转场动画

本文系统梳理 CALayer 图层体系与 Core Animation 核心动画:CALayer 常用属性、position/anchorPoint 定位机制、隐式动画与 CATransaction、CAAnimation 继承结构、CABasicAnimation/CAKeyframeAnimation/CAAnimationGroup/CATransition 四类动画、动画暂停与恢复,以及 CADisplayLink。


一、CALayer 简介

1. 什么是 CALayer

CALayer 定义在 QuartzCore 框架中,是 UIView 内部负责显示的图层。每个 UIView 创建时内部自动创建一个 CALayer,通过 view.layer 访问:

@property(nonatomic, readonly, retain) CALayer *layer;

UIView 负责事件处理与层级管理,CALayer 负责内容显示与动画。UIView 调用 drawRect: 绘图时,内容绘制在自身 layer 上,由 Core Animation 渲染服务呈现到屏幕。

2. CALayer 的基本用途

通过操作 CALayer 可以调整 UIView 外观:圆角、边框、阴影、形变,以及添加动画实现炫酷效果。


二、CALayer 常用属性

属性 类型 说明
bounds CGRect 宽度和高度(x/y 通常为 0)
position CGPoint 在父层中的位置(默认指中点,由 anchorPoint 决定)
anchorPoint CGPoint 锚点(x/y 范围 0~1,默认 0.5,0.5),决定 position 对应图层上的哪个点
backgroundColor CGColorRef 背景颜色
transform CATransform3D 3D 形变(平移/缩放/旋转)
borderColor CGColorRef 边框颜色
borderWidth CGFloat 边框宽度
cornerRadius CGFloat 圆角半径
contents id 图层内容(可设为 CGImageRef)
masksToBounds BOOL 是否裁剪超出图层边界的子层内容
shadowOpacity float 阴影透明度(0~1,默认 0 即无阴影)
shadowColor CGColorRef 阴影颜色
shadowOffset CGSize 阴影偏移量
shadowRadius CGFloat 阴影模糊半径
opacity float 图层透明度(0~1)
zPosition CGFloat 在父层中的 Z 轴层级(影响显示顺序,不影响命中测试)
hidden BOOL 是否隐藏
sublayers NSArray 子图层数组
superlayer CALayer 父图层
mask CALayer 蒙版图层(根据透明度裁剪)

基本使用

// 圆角
self.redView.layer.cornerRadius = 10;
self.redView.layer.masksToBounds = YES; // 裁剪超出圆角的内容

// 边框
self.redView.layer.borderWidth = 2;
self.redView.layer.borderColor = [UIColor blackColor].CGColor;

// 阴影(注意:阴影与 masksToBounds=YES 冲突,阴影会被裁剪掉)
self.redView.layer.shadowOpacity = 0.5;
self.redView.layer.shadowColor = [UIColor blackColor].CGColor;
self.redView.layer.shadowOffset = CGSizeMake(2, 2);
self.redView.layer.shadowRadius = 3;

// 内容(设置图片)
self.layer.contents = (id)[UIImage imageNamed:@"avatar"].CGImage;

cornerRadius 只影响背景色和边框,不影响 contents(图片)和子层,需配合 masksToBounds=YES 才能裁剪图片圆角。阴影需要图层外部空间,masksToBounds=YES 会裁掉阴影,二者不可同时生效。


三、UIView 与 CALayer 的关系与选择

1. 框架可移植性

  • CALayerCGImageRefCGColorRef 定义在 QuartzCore / CoreGraphics 框架,可跨平台(iOS + macOS)。
  • UIColorUIImage 定义在 UIKit,仅 iOS 可用。
  • 为保证可移植性,CALayer 的颜色/内容属性使用 CGColorRef/CGImageRef,不使用 UIColor/UIImage

2. 选择 UIView 还是 CALayer

  • CALayer 继承自 NSObject不能响应触摸事件(不是 UIResponder 子类)。
  • UIView 继承自 UIResponder,可以响应事件。
  • 需要交互 → 用 UIView;不需要交互、纯显示 → CALayer 性能更优(更轻量级)。

Core Animation 动画直接作用在 CALayer 上,对 UIView 做核心动画本质上是对 view.layer 做动画。


四、position 与 anchorPoint

positionanchorPoint 是 CALayer 定位的核心:

  • position:图层在父层中的位置,以父层左上角为原点
  • anchorPoint:锚点,决定图层上的哪个点与 position 重合。以自身左上角为原点,x/y 范围 0~1,默认 (0.5, 0.5)(中心点)。
anchorPoint(0,0) → 左上角
anchorPoint(0.5,0.5) → 中心点(默认)
anchorPoint(1,1) → 右下角

修改 anchorPoint 的注意事项

修改 anchorPoint 时,position 不变,但 anchorPoint 对应的点变了,导致图层位置发生偏移。如需保持视觉位置不变,需重新计算 position:

// 修改 anchorPoint 后保持位置不变
CGPoint oldAnchorPoint = self.layer.anchorPoint;
self.layer.anchorPoint = CGPointMake(0, 0); // 改为左上角

CGFloat deltaX = (self.layer.anchorPoint.x - oldAnchorPoint.x) * self.layer.bounds.size.width;
CGFloat deltaY = (self.layer.anchorPoint.y - oldAnchorPoint.y) * self.layer.bounds.size.height;
self.layer.position = CGPointMake(self.layer.position.x + deltaX, self.layer.position.y + deltaY);

frame 是计算属性

CALayer 的 frame 不是存储属性,而是由 positionanchorPointboundstransform 计算得出。修改 transform(如旋转)后,frame 的值可能变得无意义,此时应使用 bounds + position 定位。


五、隐式动画与 CATransaction

1. 隐式动画

每个 UIView 内部关联的 layer 称为 Root Layer(根层)。手动创建的 CALayer(非 Root Layer)的部分属性修改时,默认自动产生动画,称为隐式动画。这些属性称为 Animatable Properties(可动画属性)

可动画属性 动画效果
bounds 缩放
backgroundColor 背景色渐变
position 平移
opacity 淡入淡出
cornerRadius 圆角变化
borderWidth 边框宽度变化
transform 形变

Root Layer(UIView 内部的 layer)默认关闭隐式动画,因为 UIView 的动画方法(UIView animateWithDuration:)内部通过 CATransaction 管理动画。直接修改 Root Layer 属性不产生隐式动画。

2. CATransaction 动画事务

通过 CATransaction 可以控制隐式动画的开关、时长、动画曲线:

// 关闭隐式动画
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.myLayer.position = CGPointMake(10, 10);
[CATransaction commit];

// 设置隐式动画时长和曲线
[CATransaction begin];
[CATransaction setAnimationDuration:1.0];
[CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
self.myLayer.backgroundColor = [UIColor blueColor].CGColor;
self.myLayer.position = CGPointMake(200, 200);
[CATransaction commit];

3. 隐式动画示例(随机位置小游戏)

072208431908890

@interface ViewController ()
@property (nonatomic, weak) CALayer *layer1;
@property (nonatomic, weak) CALayer *layer2;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    for (int i = 0; i < 2; i++) {
        CALayer *layer = [CALayer layer];
        layer.bounds = CGRectMake(0, 0, 100, 100);
        layer.position = CGPointMake(50, 50 + i * 110);
        layer.anchorPoint = CGPointMake(0, 0);
        layer.backgroundColor = (i == 0 ? [UIColor redColor] : [UIColor yellowColor]).CGColor;
        [self.view.layer addSublayer:layer];
        if (i == 0) self.layer1 = layer;
        else self.layer2 = layer;
    }
}

- (void)animateLayer:(CALayer *)layer {
    [CATransaction begin];
    [CATransaction setAnimationDuration:1];
    layer.position = CGPointMake(arc4random_uniform(275), arc4random_uniform(550));
    layer.backgroundColor = [self randomColor].CGColor;
    layer.cornerRadius = arc4random_uniform(50);
    layer.borderColor = [self randomColor].CGColor;
    layer.borderWidth = arc4random_uniform(10);
    [CATransaction commit];
}

- (IBAction)start:(id)sender {
    [self animateLayer:self.layer1];
    [self animateLayer:self.layer2];
}

- (UIColor *)randomColor {
    CGFloat r = arc4random_uniform(256) / 255.0;
    CGFloat g = arc4random_uniform(256) / 255.0;
    CGFloat b = arc4random_uniform(256) / 255.0;
    return [UIColor colorWithRed:r green:g blue:b alpha:1];
}

@end

六、Core Animation 概述

1. 继承结构

CAAnimation(抽象类,遵守 CAMediaTiming 协议)
├── CAPropertyAnimation(抽象类)
│   ├── CABasicAnimation      基本动画
│   └── CAKeyframeAnimation   关键帧动画
├── CAAnimationGroup           动画组
└── CATransition               转场动画

072211106124051

2. 使用步骤

  1. 有一个 CALayer;
  2. 创建 CAAnimation 子类对象,设置动画属性;
  3. 调用 [layer addAnimation:forKey:] 添加动画到图层;
  4. 调用 [layer removeAnimationForKey:] 可移除动画。

Xcode 5 之后项目自动链接 QuartzCore,无需手动添加框架;仍需 #import <QuartzCore/QuartzCore.h>

3. CAAnimation 基类属性

属性 说明
duration 动画持续时间
repeatCount 重复次数(无限循环设 HUGE_VALFMAXFLOAT
repeatDuration 重复时间(与 repeatCount 互斥)
autoreverses 动画结束后是否原路返回
removedOnCompletion 动画结束后是否从图层移除(默认 YES)
fillMode 非 active 时间段的行为(开始前/结束后)
beginTime 延迟执行时间(CACurrentMediaTime() + 延迟
timingFunction 速度控制函数
delegate 动画代理

4. fillMode 填充模式

fillMode 生效需配合 removedOnCompletion = NO

fillMode 说明
kCAFillModeRemoved 默认值,动画开始前和结束后对 layer 无影响,结束后恢复原状
kCAFillModeForwards 动画结束后保持最后状态
kCAFillModeBackwards 动画加入 layer 后立即进入初始状态并等待开始
kCAFillModeBoth Forwards + Backwards 合成

即使设置 removedOnCompletion=NO + fillMode=Forwards,图层的真实属性值并未改变,只是视觉上保持动画结束状态。如需真实改变属性,应在动画开始前直接设置 layer 的目标属性值。

5. timingFunction 速度控制

常量 说明
kCAMediaTimingFunctionLinear 匀速
kCAMediaTimingFunctionEaseIn 先慢后快(渐进)
kCAMediaTimingFunctionEaseOut 先快后慢(渐出)
kCAMediaTimingFunctionEaseInEaseOut 先慢后快再慢(默认)
kCAMediaTimingFunctionDefault 默认,中间较快

6. 动画代理

@interface NSObject (CAAnimationDelegate)
- (void)animationDidStart:(CAAnimation *)anim;
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;
@end

7. 动画的暂停与恢复

// 暂停
- (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;               // 补偿暂停时间
}

七、CABasicAnimation 基本动画

CABasicAnimationCAPropertyAnimation 的子类,通过 keyPath 指定图层属性,从 fromValue 渐变到 toValue

常用 keyPath

keyPath 说明
position 位置(平移)
bounds 尺寸(缩放)
transform.translation.x/y 平移 x/y
transform.scale 缩放
transform.rotation.z 旋转(z 轴,平面旋转)
opacity 透明度
backgroundColor 背景色
cornerRadius 圆角
borderWidth 边框宽度
shadowOpacity 阴影透明度

示例:缩放动画

CABasicAnimation *anim = [CABasicAnimation animation];
anim.keyPath = @"transform.scale";
anim.fromValue = @1.0;
anim.toValue = @0.5;
anim.duration = 0.5;
anim.removedOnCompletion = NO;
anim.fillMode = kCAFillModeForwards;
[self.imageView.layer addAnimation:anim forKey:nil];

示例:旋转动画

CABasicAnimation *anim = [CABasicAnimation animation];
anim.keyPath = @"transform.rotation.z";
anim.toValue = @(M_PI); // 旋转 180°
anim.duration = 1.0;
[self.view.layer addAnimation:anim forKey:nil];

byValue 表示相对变化量(如 @(M_PI) 表示在当前基础上再转 180°)。fromValue+toValue 为绝对变化,fromValue+byValue 为相对变化,toValue+byValue 不常用。


八、CAKeyframeAnimation 关键帧动画

CAKeyframeAnimation 通过 values 数组保存多个关键帧,图层依次经过每个关键帧;也可通过 path 指定移动轨迹。

属性 说明
values 关键帧值数组
path 移动路径(CGPathRef),设置后 values 被忽略,仅对 position/anchorPoint 生效
keyTimes 每个关键帧对应的时间点(0~1),不设置则时间平分

示例:左右摇晃

072214147686558

CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
anim.keyPath = @"transform";
anim.duration = 0.5;
anim.repeatCount = MAXFLOAT;

NSValue *v1 = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(-25 / 180.0 * M_PI, 0, 0, 1)];
NSValue *v2 = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(25 / 180.0 * M_PI, 0, 0, 1)];
NSValue *v3 = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(-25 / 180.0 * M_PI, 0, 0, 1)];
anim.values = @[v1, v2, v3];

[self.layer addAnimation:anim forKey:nil];

示例:沿路径移动

CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
anim.keyPath = @"position";
anim.duration = 3.0;

UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(50, 50, 200, 200)];
anim.path = path.CGPath;

[self.imageView.layer addAnimation:anim forKey:nil];

CABasicAnimation 可看作只有 2 个关键帧的 CAKeyframeAnimation


九、CAAnimationGroup 动画组

CAAnimationGroup 将多个动画对象组合,同时并发执行

// 缩放 + 旋转 + 平移 同时执行
CABasicAnimation *scaleAnim = [CABasicAnimation animation];
scaleAnim.keyPath = @"transform.scale";
scaleAnim.toValue = @0.5;

CABasicAnimation *rotateAnim = [CABasicAnimation animation];
rotateAnim.keyPath = @"transform.rotation.z";
rotateAnim.toValue = @(M_PI);

CABasicAnimation *positionAnim = [CABasicAnimation animation];
positionAnim.keyPath = @"position";
positionAnim.toValue = [NSValue valueWithCGPoint:CGPointMake(200, 300)];

CAAnimationGroup *group = [CAAnimationGroup animation];
group.animations = @[scaleAnim, rotateAnim, positionAnim];
group.duration = 1.0;
group.removedOnCompletion = NO;
group.fillMode = kCAFillModeForwards;

[self.imageView.layer addAnimation:group forKey:nil];

组内各动画的 duration 不生效,以 CAAnimationGroup.duration 为准。可通过设置子动画的 beginTime 错开执行时间。


十、CATransition 转场动画

CATransition 用于图层的转场效果(移出/移入屏幕),UINavigationController 的 push 动画即通过 CATransition 实现。

属性 说明
type 转场类型
subtype 转场方向
startProgress 动画起点(0~1)
endProgress 动画终点(0~1)

公开的转场类型(type)

072217442687488

常量 说明
kCATransitionFade 淡入淡出
kCATransitionMoveIn 新图覆盖旧图
kCATransitionPush 推挤(旧图被推出)
kCATransitionReveal 揭开(旧图移开露出新图)

转场方向(subtype)

常量 说明
kCATransitionFromRight 从右侧
kCATransitionFromLeft 从左侧
kCATransitionFromTop 从顶部
kCATransitionFromBottom 从底部

示例:图片切换转场

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    static int index = 1;
    NSString *imageName = [NSString stringWithFormat:@"%d", index];
    self.imageView.image = [UIImage imageNamed:imageName];
    index = (index % 3) + 1;
    
    CATransition *anim = [CATransition animation];
    anim.type = kCATransitionPush;          // 推挤
    anim.subtype = kCATransitionFromLeft;   // 从左侧
    anim.duration = 0.5;
    [self.imageView.layer addAnimation:anim forKey:nil];
}

072215203308794

072215385802152

072215581592537

转场代码(修改图层内容)与转场动画必须写在一起,否则无效。

关于私有转场效果

cube(立方体)、suckEffect(吮吸)、oglFlip(翻转)、rippleEffect(波纹)、pageCurl(翻页)、pageUnCurl(反翻页)、cameraIrisHollowOpen/Close(镜头开关)等效果属于私有 API,没有公开的常量定义,需用字符串字面量。使用私有 API 可能导致 App Store 审核被拒,生产环境建议使用公开类型或自定义转场。


十一、UIView 转场动画

UIKit 封装了基于 CATransition 的转场方法,使用更简便:

单视图转场

[UIView transitionWithView:self.imageView
                  duration:0.5
                   options:UIViewAnimationOptionTransitionFlipFromLeft
                animations:^{
                    // 在此修改视图内容
                    self.imageView.image = [UIImage imageNamed:@"newImage"];
                } completion:^(BOOL finished) {
                    // 动画完成
                }];

双视图转场(替换视图)

[UIView transitionFromView:self.oldView
                    toView:self.newView
                  duration:0.5
                   options:UIViewAnimationOptionTransitionCurlUp
                completion:^(BOOL finished) {
                    // 动画完成,oldView 已被移除,newView 已被添加
                }];

常用转场 options

常量 说明
UIViewAnimationOptionTransitionFlipFromLeft 从左翻转
UIViewAnimationOptionTransitionFlipFromRight 从右翻转
UIViewAnimationOptionTransitionCurlUp 向上翻页
UIViewAnimationOptionTransitionCurlDown 向下翻页
UIViewAnimationOptionTransitionCrossDissolve 淡入淡出

CADisplayLink 是与屏幕刷新频率同步的定时器(约 60 次/秒),适合频繁重绘的动画,比 NSTimer 更精准、无延迟。

- (void)awakeFromNib {
    CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(setNeedsDisplay)];
    [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
    // 停止:[link invalidate];
}

setNeedsDisplay 不会立即调用 drawRect:,而是给控件绑定重绘标识,屏幕刷新时统一重绘。UIScrollView 滚动时需使用 NSRunLoopCommonModes 才能继续执行。


十三、Swift 版本对照

CALayer 属性

let view = UIView()
view.layer.cornerRadius = 10
view.layer.masksToBounds = true
view.layer.borderWidth = 2
view.layer.borderColor = UIColor.black.cgColor
view.layer.shadowOpacity = 0.5
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOffset = CGSize(width: 2, height: 2)
view.layer.shadowRadius = 3
view.layer.contents = UIImage(named: "avatar")?.cgImage

隐式动画(CATransaction)

CATransaction.begin()
CATransaction.setAnimationDuration(1.0)
CATransaction.setDisableActions(false)
layer.position = CGPoint(x: 200, y: 200)
layer.backgroundColor = UIColor.blue.cgColor
CATransaction.commit()

CABasicAnimation

let anim = CABasicAnimation(keyPath: "transform.scale")
anim.fromValue = 1.0
anim.toValue = 0.5
anim.duration = 0.5
anim.isRemovedOnCompletion = false
anim.fillMode = .forwards
imageView.layer.add(anim, forKey: nil)

CAKeyframeAnimation

let anim = CAKeyframeAnimation(keyPath: "transform")
anim.duration = 0.5
anim.repeatCount = .infinity
let v1 = NSValue(caTransform3D: CATransform3DMakeRotation(-25 / 180 * .pi, 0, 0, 1))
let v2 = NSValue(caTransform3D: CATransform3DMakeRotation(25 / 180 * .pi, 0, 0, 1))
anim.values = [v1, v2, v1]
layer.add(anim, forKey: nil)

CAAnimationGroup

let scale = CABasicAnimation(keyPath: "transform.scale")
scale.toValue = 0.5
let rotate = CABasicAnimation(keyPath: "transform.rotation.z")
rotate.toValue = CGFloat.pi
let group = CAAnimationGroup()
group.animations = [scale, rotate]
group.duration = 1.0
group.isRemovedOnCompletion = false
group.fillMode = .forwards
imageView.layer.add(group, forKey: nil)

CATransition

let anim = CATransition()
anim.type = .push
anim.subtype = .fromLeft
anim.duration = 0.5
imageView.image = UIImage(named: "new")
imageView.layer.add(anim, forKey: nil)

动画暂停与恢复

func pauseLayer(_ layer: CALayer) {
    let pausedTime = layer.convertTime(CACurrentMediaTime(), from: nil)
    layer.speed = 0
    layer.timeOffset = pausedTime
}

func resumeLayer(_ layer: CALayer) {
    let pausedTime = layer.timeOffset
    layer.speed = 1
    layer.timeOffset = 0
    layer.beginTime = 0
    let timeSincePause = layer.convertTime(CACurrentMediaTime(), from: nil) - pausedTime
    layer.beginTime = timeSincePause
}

十四、总结

  • CALayer:UIView 内部负责显示的图层,继承 NSObject,不能响应事件;可设置圆角、边框、阴影、形变、内容等。
  • position/anchorPoint:position 是图层在父层的位置,anchorPoint 决定图层上哪个点与 position 重合(默认中心点);修改 anchorPoint 会导致位置偏移,需重新计算 position;frame 是计算属性,transform 后可能无效。
  • 隐式动画:手动创建的非 Root Layer 修改可动画属性时默认产生动画;Root Layer(UIView 内部 layer)隐式动画被禁用;通过 CATransaction 控制开关、时长、曲线。
  • Core Animation:直接作用于 CALayer,后台执行不阻塞主线程;继承结构 CAAnimation → CAPropertyAnimation → CABasicAnimation/CAKeyframeAnimation,以及 CAAnimationGroup、CATransition。
  • CABasicAnimation:通过 keyPath 指定属性,fromValue→toValue 渐变;动画结束后图层真实属性值未变,只是视觉保持(removedOnCompletion=NO + fillMode=Forwards)。
  • CAKeyframeAnimation:values 多关键帧或 path 路径移动,keyTimes 指定各帧时间点。
  • CAAnimationGroup:多动画并发执行,duration 以组为准,子动画 beginTime 可错开。
  • CATransition:转场动画,公开 type 有 Fade/MoveIn/Push/Reveal,subtype 指定方向;cube/rippleEffect 等为私有 API 可能被拒;转场代码与动画必须写在一起。
  • UIView 转场:transitionWithView:(单视图)和 transitionFromView:toView:(双视图)封装了 CATransition,使用更简便。
  • 动画暂停恢复:通过 speed=0/timeOffset 暂停,恢复时补偿 beginTime。
  • CADisplayLink:与屏幕刷新同步(60fps),适合频繁重绘;UIScrollView 滚动时需 NSRunLoopCommonModes。

posted @ 2015-08-07 22:20  Mr.陳  阅读(727)  评论(0)    收藏  举报