我无法谈论 iOS 5.xv 6.x 的差异,但是当我使用 时CADisplayLink
,我从来没有在每次迭代时对诸如“移动 x 像素/点”之类的东西进行硬编码,而是查看timestamp
(或更准确地说,增量在我的初始值timestamp
和当前值之间timestamp
)并根据经过的时间而不是经过的帧数来计算位置。这样,帧速率不会影响运动的速度,而只会影响运动的平滑度。(而且 30 和 29 之间的差异可能无法区分。)
引用CADisplayLink 类参考:
一旦显示链接与运行循环相关联,当屏幕内容需要更新时,就会调用目标上的选择器。目标可以读取显示链接的时间戳属性来检索上一帧的显示时间。例如,显示电影的应用程序可能会使用时间戳来计算接下来将显示哪个视频帧。执行自己的动画的应用程序可能会使用时间戳来确定显示的对象在即将到来的帧中出现的位置和方式。持续时间属性提供帧之间的时间量。您可以在应用程序中使用此值来计算显示的帧速率、显示下一帧的大致时间,并调整绘图行为以便及时准备好下一帧以显示。
作为一个随机示例,这里我UIBezierPath
使用经过的秒数作为参数来制作动画。
或者,如果您正在处理一系列UIImage
帧,则可以按如下方式计算帧号:
@property (nonatomic) CFTimeInterval firstTimestamp;
- (void)handleDisplayLink:(CADisplayLink *)displayLink
{
if (!self.firstTimestamp)
self.firstTimestamp = displayLink.timestamp;
CFTimeInterval elapsed = (displayLink.timestamp - self.firstTimestamp);
NSInteger frameNumber = (NSInteger)(elapsed * kFramesPerSecond) % kMaxNumberOfFrames;
// now do whatever you want with this frame number
}
或者,更好的是,为了避免丢失帧的风险,继续让它以 60 fps 运行,然后确定帧是否需要更新,这样你就可以降低丢帧的风险。
- (void)handleDisplayLink:(CADisplayLink *)displayLink
{
if (!self.firstTimestamp)
self.firstTimestamp = displayLink.timestamp;
CFTimeInterval elapsed = (displayLink.timestamp - self.firstTimestamp);
NSInteger frameNumber = (NSInteger)(elapsed * kFramesPerSecond) % kMaxNumberOfFrames;
if (frameNumber != self.lastFrame)
{
// do whatever you want with this frame number
...
// now update the "lastFrame" number property
self.lastFrame = frameNumber;
}
}
但通常,根本不需要帧号。例如,要将 a 移动UIView
一个圆圈,您可以执行以下操作:
- (void)handleDisplayLink:(CADisplayLink *)displayLink
{
if (!self.firstTimestamp)
self.firstTimestamp = displayLink.timestamp;
CFTimeInterval elapsed = (displayLink.timestamp - self.firstTimestamp);
self.animatedView.center = [self centerAtElapsed:elapsed];
}
- (CGPoint)centerAtElapsed:(CFTimeInterval)elapsed
{
CGFloat radius = self.view.bounds.size.width / 2.0;
return CGPointMake(radius + sin(elapsed) * radius,
radius + cos(elapsed) * radius);
}
顺便说一句,如果您使用 Instruments 来测量帧速率,它看起来可能比在设备上实际运行时要慢。对于马特的评论,为了获得准确的帧速率,您应该在具有发布版本的实际设备上以编程方式对其进行测量。