核心动画

图层 CALayer 对象

简介

iOS中,我们能看得见的东西,基本上都是 UIView,比如一个按钮,一个文本标签。而 UIView 之所以能显示在屏幕上,完全时因为它内部的一个图层。

在创建UIView对象时,内部会自动创建图层 CALayer 对象,通过 UIView 的 layer 属性可以访问这个图层对象。

当 UIView 需要显示到屏幕上时,会调用 drawRect: 方法进行绘图,并且会将所有内部内容绘制在自己的图层上,绘图之后,系统会将图层拷贝到屏幕上,于是就完成了 UIView 的显示。

iOS7 以前需要导入框架,QuartzCore.framework

QuartzCore 框架和 CoreGraphics 框架是可以跨平台使用的,在 iOS 和 Mac OS X 上都能使用。

CALayer 属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 宽度和高度
@property CGRect bounds;

// 位置 用来设置 CALayer 在父层中的位置 (配合 anchorPoint 确定图层在控件中的位置)
@property CGPoint position;

// 锚点(x,y的范围都是0-1),决定着 CALayer身上 的哪个点会在 position 属性所指的位置,默认值为(0.5, 0.5)
@property CGPoint anchorPoint;

// 背景颜色(CGColorRef类型)
@property CGColorRef backgroundColor;

// 内容(比如设置为图片CGImageRef)
@property(retain) id contents;

@property CGFloat borderWidth; // 边框宽度
// 注意:边框加在 view 里面

@property(nullable) CGColorRef borderColor; // 边框颜色
self.purpleView.layer.borderColor = [UIColor greenColor].CGColor;

@property CGFloat cornerRadius; // view 的圆角
self.purpleView.layer.masksToBounds = NO; // 超出 View 裁剪 默认时 NO


@property(nullable) CGColorRef shadowColor; // 阴影颜色
@property(nonatomic) CGSize shadowOffset; // default is CGSizeMake(0, -1)
// CGSizeMake(x, y),阴影向右边偏移了 x,向下偏移了 y
@property float shadowOpacity; // 阴影不透明度

// 注意:阴影的 颜色、偏差和阴影不透明度 配合使用才有效果


@property CATransform3D transform; // 核心属性 3D

// 3D 旋转参数的意思是,从原点到(1, 1, 0)的线为轴,旋转 M_PI_4;
self.purpleView.layer.transform = CATransform3DMakeRotation(M_PI_4, 1, 1, 0);

self.purpleView.layer.transform = CATransform3DMakeScale(1.5, 0.5, 0);

[self.purpleView.layer setValue:@(M_PI_2) forKeyPath:@"transform.rotation"];

[self.purpleView.layer setValue:[NSValue valueWithCATransform3D:CATransform3DMakeScale(0.5, 2, 0)] forKeyPath:@"transform"];

// 可以传哪些 key path 在官方文档 CATransform3D key paths
[self.purpleView.layer setValue:@(0.5) forKeyPath:@"transform.scale.y"];

只要是继承了 UIView,都有图层属性,因此 imageView,button 都可以设置以上属性。

在设置 imageView 的圆角时,由于 imageView 内部还有一个用于显示
图片的子层(UIView 内部只有一个主图层),因此我们改变 imageView 的 Layer时,改变的是主层的圆角。子层显示在主层上,因此图片并没有圆角效果,只有再加一句代码:

1
imageView.layer.masksToBounds = NO; // 超出 View 裁剪 默认时 NO

告诉 imageView 超出主层的部分全部减掉。才会显示圆角效果。

CALayer 使用注意点:

UIView 可以通过 subViews 属性访问所有的子视图,CALayer 也可以通过 subLayers 属性访问所有的子层

UIView 可以通过 superView 属性访问父视图,CALayer 也可以通过 superLayer 访问父层。

如果两个 UIView 是父子关系,那么它们内部的 CALayer 也是父子关系

图层的手动创建

1
2
3
4
5
6
7
8

// 创建图层
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(50, 50, 200, 200);
layer.backgroundColor = [UIColor redColor].CGColor;
// 设置图层内容
layer.contents = (id)[UIImage imageNamed:@"阿狸头像"].CGImage;
[self.view.layer addSublayer:layer];

自定义图层的内容

方法一:自己写一个类继承 CALayer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#import "MyLayer.h"

@implementation MyLayer

- (void)drawInContext:(CGContextRef)ctx
{
// 红色
CGContextSetRGBFillColor(ctx, 1, 0, 0, 1);
// 添加圆
CGContextAddEllipseInRect(ctx, CGRectMake(0, 0, 50, 50));
// 实心绘制
CGContextFillPath(ctx);
}

@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void)viewDidLoad {
[super viewDidLoad];
self.purpleView.hidden = YES;

MyLayer *layer = [MyLayer layer];
layer.bounds = CGRectMake(0, 0, 100, 100);
layer.position = CGPointZero;
layer.anchorPoint = CGPointZero;
layer.backgroundColor = [UIColor blueColor].CGColor;

// 有这句话才能执行 -drawInContext 和 drawRect 方法
[layer setNeedsDisplay];
[self.view.layer addSublayer:layer];
}

方法二:实现代理方法

CALayer 的代理是没有协议的,这说明任何对象都可以当 CALayer 代理。而代理方法就在 NSObject 中。当图层需要显示的时候,会调用代理的绘制方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- (void)viewDidLoad {
[super viewDidLoad];
self.purpleView.hidden = YES;
CALayer *layer = [CALayer layer];
layer.bounds = CGRectMake(0, 0, 100, 100);
layer.position = CGPointZero;
layer.anchorPoint = CGPointZero;
layer.delegate = self;
layer.backgroundColor = [UIColor blueColor].CGColor;
[layer setNeedsDisplay];

[self.view.layer addSublayer:layer];
}

#pragma mark - CALayer 的代理方法
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
// 红色
CGContextSetRGBFillColor(ctx, 1, 0, 0, 1);
// 添加圆
CGContextAddEllipseInRect(ctx, CGRectMake(0, 0, 50, 50));
// 实心绘制
CGContextFillPath(ctx);
}

CALayer 上动画的暂停和恢复

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#pragma mark 暂停CALayer的动画
- (void)pauseLayer:(CALayer*)layer
{
CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];

// 让CALayer的时间停止走动
layer.speed = 0.0;
// 让CALayer的时间停留在pausedTime这个时刻
layer.timeOffset = pausedTime;
}

#pragma mark 恢复CALayer的动画
-(void)resumeLayer:(CALayer*)layer
{
CFTimeInterval pausedTime = layer.timeOffset;
// 1. 让CALayer的时间继续行走
layer.speed = 1.0;
// 2. 取消上次记录的停留时刻
layer.timeOffset = 0.0;
// 3. 取消上次设置的时间
layer.beginTime = 0.0;
// 4. 计算暂停的时间(这里也可以用CACurrentMediaTime()-pausedTime)
CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
// 5. 设置相对于父坐标系的开始时间(往后退timeSincePause)
layer.beginTime = timeSincePause;
}

UIView 能够显示内容的原理

首先 view.layer.delegate == view,一个 view 内部的图层的代理就是这个 view。view.layer 会准备一个 Layer Graphics Contex(图层类型上下文)

这时要显示内容时会调用 view.layer.delegate 的 -drawLayer:inContext: 方法将图层上的东西绘制出来,而 drawLayer:inContext: 方法内部又回调用 -drawRect: 方法。

因此 view 就可以在 -drawRect: 方法中实现绘图代码,所有东西最终都绘制到 view.layer 上面,系统再将 view.layer 的内容拷贝到屏幕,于是完成了 view 的显示。


隐式动画

每一个 UIView 内部都默认关联着一个 CALayer,我们可以称这个 Layer 为 Root Layer(根层)

所有的非 Root Layer,就是那些手动创建的 CALayer 对象,都存在着隐式动画

当我们对于非 Root Layer 的部分属性进行修改时,默认会自动产生一些动画效果。这些属性称为 Animatable Properties(可动画属性)比如:

  • bounds:用于设置 CALayer 的宽度和高度,修改这个属性会产生缩放动画
  • backgroundColor:用于设置 CALayer 的背景色,修改这个属性会产生背景色的渐变动画
  • position:用于设置 CALayer 的位置,修改这个属性会产生平移动画

代码如下:

首先,需要新建图层,因为只有非根层才有隐式动画。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)viewDidLoad {
[super viewDidLoad];

self.purpleView.hidden = YES;

CALayer *layer = [CALayer layer];

layer.bounds = CGRectMake(0, 0, 100, 100);
layer.backgroundColor = [UIColor redColor].CGColor;
layer.position = CGPointZero;
layer.anchorPoint = CGPointZero;
[self.view.layer addSublayer:layer];

self.layer = layer;

}

当我们点击屏幕时候要做一些事情:

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
self.layer.backgroundColor = [UIColor blueColor].CGColor;
self.layer.opacity = 0.5;
self.layer.position = CGPointMake(100, 100);

// 如果要关掉动画,将代码放到下面事务内部:
[CATransaction begin]; // 开启事务
[CATransaction setDisableActions:YES];
self.layer.position = CGPointMake(100, 100);
self.layer.opacity = 0.5;
[CATransaction commit]; // 提交事务
}

Core Animation

Core Animation简介:

Core Animation 可以用在 Mac OS X 和 iOS 平台。
动画执行过程都是在后台操作,不会阻塞主线程。
Core Animation 直接作用在 CALayer 上,并非 UIView。

使用步骤:

  1. 需要 QuartzCore.framework 框架 <QuartzCore/QuartzCore.h>

  2. 初始化一个CAAnimation 对象,设置动画相关属性。

  3. 通过调用 CALayer 的 addAnimation:forKey: 方法增加 CAAnimation 对象到 CALayer 中,这样就能开始执行动画了,key 为该动画的标识符,删除动画时会用到。

  4. 通过调用 CALayer 的 removeAnimationForKey: 方法可以停止 CALayer 中的动画。

CAAnimation 内部结构

CAAnimation 是抽象类

其实,一般情况下,我们使用的比较多的是CAAnimation的子类,因此,先大致看看CAAnimation的继承结构:

黑线代表继承,黑色文字代表类名,白色文字代表属性。其中CAMediaTiming是一个协议(protocol)。

动画代理方法

property delegate;

1
2
3
4
5
6
7
@interface NSObject (CAAnimationDelegate)
// 动画开始执行的时候触发这个方法
- (void)animationDidStart:(CAAnimation *)anim;

// 动画执行完毕的时候触发这个方法
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;
@end

CAAnimation 的常用属性

  • 可用的keyPath
1
2
3
4
5
6
7
8
9
10
11
12
13
// 动画进度,从动画的百分之80开始执行动画
animation.startProgress = 0;
// 从 startProgress 开始 以 endProgress 结束
// 以动画的百分之 startProgress 开始 以 动画的 endProgress 结束
animation.endProgress = 0.6;
animation.duration = 2.0;
// 完成时会移除动画,默认值为 YES
animation.removedOnCompletion = NO;
// fillMode 动画执行完毕后的绘制模式
animation.fillMode = kCAFillModeForwards;

// 设置动画的执行节奏
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
  • fillMode 动画执行完毕后的绘制模式
    • kCAFillModeForwards 当动画结束后,layer会一直保持着动画最新的状态
    • kCAFillModeBackwards 与 kCAFillModeForwards 相对应,表示只要一开始执行动画(-addAnimation: 方法),便会让 layer 立即进入初始位置(fromValue)
    • kCAFillModeRemoved 这个是默认值,动画开始前和动画结束后,动画对layer都没有影响,动画结束后,layer会恢复到之前的状态
    • kCAFillModeBoth 动画加入后开始之前,layer便处于动画初始状态,动画结束后layer保持动画最后的状态。

==其中 transform 属性传入的是 CATransform3D==

  • timingFunction 属性可选的值有:

    • kCAMediaTimingFunctionLinear(线性):匀速,给你一个相对静态的感觉
    • kCAMediaTimingFunctionEaseIn(渐进):动画缓慢进入,然后加速离开
    • kCAMediaTimingFunctionEaseOut(渐出):动画全速进入,然后减速的到达目的地
    • kCAMediaTimingFunctionEaseInEaseOut(渐进渐出):动画缓慢的进入,中间加速,然后减速的到达目的地。这个是默认的动画行为。

CAAnimation 的子类们

CATransition(transition 有过渡的意思)(重要)

作用:转场动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

// 转场动画
CATransition *animation = [CATransition animation];

// 关于 type 见下面的图表
animation.type = @"moveIn";

// subtype 控制转场方向:
/*
kCATransitionFade:
kCATransitionFromBottom: 从上往下
kCATransitionFromTop: 从下往上
kCATransitionFromLeft: 从左往右
kCATransitionFromRight: 从右往左
*/
animation.subtype = kCATransitionFromBottom;

animation.duration = 0.5;

// 动画进度,从动画的百分之80开始执行动画
animation.startProgress = 0;
// 从 startProgress 开始 以 endProgress 结束
// 以动画的百分之 startProgress 开始 以 动画的 endProgress 结束
animation.endProgress = 0.6;

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

CABasicAnimation基本动画

  • 代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 1.创建动画对象
CABasicAnimation *animation = [CABasicAnimation animation];
// 2.设置动画属性

// keyPath 决定了执行怎样的动画,调整哪个属性来执行动画
animation.keyPath = @"position";

// 如果没有 fromValue 属性,表示从当前位置开始 类型 id 意味着只能传入对象
animation.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, 0)];
animation.toValue = [NSValue valueWithCGPoint:CGPointMake(200, 300)];

// byValue 表示递增值,表示在自己的基础上增加多少值
animation.byValue;

animation.duration = 2.0;

// animation.repeatCount 动画运行次数

// 如果只写到这里,做完动画会返回原样
// 完成时会移除动画,默认值为 YES
animation.removedOnCompletion = NO;
// fillMode 动画执行完毕后的绘制模式
animation.fillMode = kCAFillModeForwards;
// 3.添加动画
[self.layer addAnimation:animation forKey:nil];

CAKeyframeAnimation帧动画

可以做一些列连续顺畅的操作

  • 第一种方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
animation.keyPath = @"position";

// 绘制路径
CGMutablePathRef path = CGPathCreateMutable();

CGPathAddEllipseInRect(path, NULL, CGRectMake(100, 100, 200, 200));

animation.path = path;
CGPathRelease(path);

// 用 quartz2D 绘制路径

animation.duration = 2.0;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;

// 设置动画的执行节奏
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];

[self.purpleView.layer addAnimation:animation forKey:nil];
  • 第二种方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];

animation.keyPath = @"position";

NSValue *v1 = [NSValue valueWithCGPoint:CGPointZero];
NSValue *v2 = [NSValue valueWithCGPoint:CGPointMake(100, 100)];
NSValue *v3 = [NSValue valueWithCGPoint:CGPointMake(0, 0)];
NSValue *v4 = [NSValue valueWithCGPoint:CGPointMake(200, 300)];
NSValue *v5 = [NSValue valueWithCGPoint:CGPointMake(300, 200)];
NSValue *v6 = [NSValue valueWithCGPoint:CGPointMake(0, 0)];

animation.values = @[v1, v2, v3, v4, v5, v6];

animation.duration = 2.0;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;

// 控制每一帧的时间 表示第一帧用 1s,后面每一帧都用 0.2s
// animation.keyTimes = @[@0.5, @0.1, @0.1, @0.1, @0.1, @0.1];

[self.purpleView.layer addAnimation:animation forKey:nil];

CAAnimationGroup

动画组,可以让很多对象一起执行动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 同时缩放,平移,旋转
CAAnimationGroup *group = [CAAnimationGroup animation];

CABasicAnimation *scale = [CABasicAnimation animation];
scale.keyPath = @"transform.scale";
scale.toValue = @0.5;

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

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

group.animations = @[scale,rotation,position];

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

UIView animation

图层动画都是假象,动画执行过程中,图层的 position 属性一直没有变过

开发中用的比较多的不是图层动画,除非是转场动画。一般开发中用的比较多的是 UIView 动画。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0);

+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0); // delay = 0.0, options = 0

+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations NS_AVAILABLE_IOS(4_0); // delay = 0.0, options = 0, completion = NULL

/* Performs `animations` using a timing curve described by the motion of a spring. When `dampingRatio` is 1, the animation will smoothly decelerate to its final model values without oscillating. Damping ratios less than 1 will oscillate more and more before coming to a complete stop. You can use the initial spring velocity to specify how fast the object at the end of the simulated spring was moving before it was attached. It's a unit coordinate system, where 1 is defined as travelling the total animation distance in a second. So if you're changing an object's position by 200pt in this animation, and you want the animation to behave as if the object was moving at 100pt/s before the animation started, you'd pass 0.5. You'll typically want to pass 0 for the velocity. */
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay usingSpringWithDamping:(CGFloat)dampingRatio initialSpringVelocity:(CGFloat)velocity options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(7_0);

+ (void)transitionWithView:(UIView *)view duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void (^ __nullable)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0);

+ (void)transitionFromView:(UIView *)fromView toView:(UIView *)toView duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0); // toView added to fromView.superview, fromView removed from its superview

/* Performs the requested system-provided animation on one or more views. Specify addtional animations in the parallelAnimations block. These additional animations will run alongside the system animation with the same timing and duration that the system animation defines/inherits. Additional animations should not modify properties of the view on which the system animation is being performed. Not all system animations honor all available options.
*/
+ (void)performSystemAnimation:(UISystemAnimation)animation onViews:(NSArray<__kindof UIView *> *)views options:(UIViewAnimationOptions)options animations:(void (^ __nullable)(void))parallelAnimations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(7_0);

简介

所在框架

CADisplayLink 和其它 CoreAnimation 类一样,都是在 QuartzCore.framework 里。

功能

CADisplayLink 最主要的特征是能提供一个周期性的调用我们赋给它的 selector 的机制,从这点上看它很像定时器 NSTimer。

使用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (void)startDisplayLink
{
self.displayLink = [CADisplayLink displayLinkWithTarget:self
selector:@selector(handleDisplayLink:)];
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
}

- (void)handleDisplayLink:(CADisplayLink *)displayLink
{
//do something
}

- (void)stopDisplayLink
{
[self.displayLink invalidate];
self.displayLink = nil;
}

当把 CADisplayLink 对象 add 到 runloop 中后,selector 就能被周期性调用,类似于 NSTimer 被启动了;

执行 invalidate 操作时, CADisplayLink 对象就会从 runloop 中移除,selector 调用也随即停止,类似于 NSTimer 的 invalidate 方法。

特性

下面结合 NSTimer 来介绍 CADisplayLink,与 NSTimer 不同的地方有:

原理

CADisplayLink 是一个能让我们以和屏幕刷新率同步的频率将特定的内容画到屏幕上的定时器类。 CADisplayLink 以特定模式注册到 runloop 后, 每当屏幕显示内容刷新结束的时候,runloop 就会向 CADisplayLink 指定的 target 发送一次指定的 selector 消息, CADisplayLink 类对应的 selector 就会被调用一次。

NSTimer 以指定的模式注册到 runloop 后,每当设定的周期时间到达后,runloop 会向指定的 target 发送一次指定的 selector 消息。

周期设置方式

iOS设备的屏幕刷新频率(FPS)是60Hz,因此 CADisplayLink的selector 默认调用周期是每秒60次,这个周期可以通过 frameInterval 属性设置, CADisplayLink 的 selector 每秒调用次数 = 60 / frameInterval。比如当 frameInterval 设为 2,每秒调用就变成 30 次。因此, CADisplayLink 周期的设置方式略显不便。

精确度

iOS设备的屏幕刷新频率是固定的,CADisplayLink 在正常情况下会在每次刷新结束都被调用,精确度相当高。

NSTimer 的精确度就显得低了点,比如 NSTimer 的触发时间到的时候,runloop 如果在忙于别的调用,触发时间就会推迟到下一个 runloop 周期。更有甚者,在 OS X v10.9 以后为了尽量避免在 NSTimer 触发时间到了而去中断当前处理的任务,NSTimer 新增了 tolerance 属性,让用户可以设置可以容忍的触发的时间范围。

使用场合

从原理上不难看出, CADisplayLink 使用场合相对专一, 适合做界面的不停重绘,比如视频播放的时候需要不停地获取下一帧用于界面渲染。

NSTimer的使用范围要广泛的多,各种需要单次或者循环定时处理的任务都可以使用。

重要属性

下面不完整的列出了 CADisplayLink 的几个重要属性:

frameInterval

可读可写的 NSInteger 型值,标识间隔多少帧调用一次 selector 方法,默认值是1 ,即每帧都调用一次。

官方文档中强调,当该值被设定小于1时,结果是不可预知的。

duration

只读的 CFTimeInterval 值,表示两次屏幕刷新之间的时间间隔。需要注意的是,该属性在 target 的 selector 被首次调用以后才会被赋值。selector 的调用间隔时间计算方式是:时间 = duration × frameInterval。

现存的 iOS 设备屏幕的 FPS 都是60Hz,这一点可以从 CADisplayLink 的 duration 属性看出来。duration 的值都是0.166666…,即1/60。尽管如此,我们并没法确定苹果不会改变 FPS ,如果以后某一天将 FPS 提升到了120Hz了怎么办呢?这时,你设置了 frameInterval 属性值为2期望每秒刷新30次,却发现每秒刷新了60次,结果可想而知,出于安全考虑,还是先根据 duration 判断屏幕的 FPS 再去使用 CADisplayLink 。

timestamp

只读的 CFTimeInterval 值,表示屏幕显示的上一帧的时间戳,这个属性通常被target 用来计算下一帧中应该显示的内容。
打印 timestamp 值,其样式类似于:

179699.631584

虽然名为时间戳,但这和常见的 unix 时间戳差异很大,事实上这是 CoreAnimation 使用的时间格式。每个 CALayer 都有一个本地时间(CALayer 本地时间的具体作用会在后续文章中说明),可以获取当前 CALayer 的本地时间并打印:

1
2
CFTimeInterval localLayerTime = [myLayer convertTime:CACurrentMediaTime() fromLayer:nil];
NSLog("localLayerTime:%f",localLayerTime);

注意

iOS并不能保证能以每秒60次的频率调用回调方法,这取决于:

  • CPU的空闲程度

如果CPU忙于其它计算,就没法保证以60HZ执行屏幕的绘制动作,导致跳过若干次调用回调方法的机会,跳过次数取决CPU的忙碌程度。

  • 执行回调方法所用的时间

如果执行回调时间大于重绘每帧的间隔时间,就会导致跳过若干次回调调用机会,这取决于执行时间长短。

参考文档

  1. 官方文档

https://developer.apple.com/library/ios/documentation/QuartzCore/Reference/CADisplayLink_ClassRef/Reference/Reference.html#//apple_ref/doc/uid/TP40009031-CH1-DontLinkElementID_1

  1. 官方使用CADisplayLink播放视频的例子

https://developer.apple.com/library/ios/samplecode/AVBasicVideoOutput/Introduction/Intro.html#//apple_ref/doc/uid/DTS40013109

本文代码连结:CAAnimation

文章作者: Ammar
文章链接: http://lizhaoloveit.cn/2014/07/20/%E6%A0%B8%E5%BF%83%E5%8A%A8%E7%94%BB/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ammar's Blog
打赏
  • 微信
  • 支付宝

评论