使用核心动画层来为视图添加动画效果

8
我有一个包含启用'Wants Core Animation Layer'的NSViewNSWindow。该视图包含许多NSImageView,最初使用动画定位。当运行动画时,它非常缓慢,并且丢失了大部分帧数。然而,如果禁用'Wants Core Animation Layer',则动画可以完美地运行。我需要核心动画层,但无法弄清如何使其表现得足够好。 我能做些什么来解决性能问题吗? 以下是代码:
// AppDelegate
NSRect origin = ...;
NSTimeInterval d = 0.0;
for (id view in views)
{
    [view performSelector:@selector(animateFrom:) withObject:origin afterDelay:d];
    d += 0.05f;
}

// NSImageView+Animations
- (void)animateFrom:(NSRect)origin
{   
    NSRect original = self.frame;
    [self setFrame:origin];
    [NSAnimationContext beginGrouping];
    [[NSAnimationContext currentContext] setDuration:0.20f];
    [[self animator] setFrame:original];
    [NSAnimationContext endGrouping];
}
2个回答

10
可能是 NSTimer 影响了性能。Core Animation 通过 CAMediaTiming 协议提供了丰富的支持来控制动画的时间,你应该在你的应用程序中利用这个协议。尝试直接使用 Core Animation 而不是使用动画代理和 NSAnimationContext。如果为每个图像创建一个 CABasicAnimation 并设置它的 beginTime,它将延迟动画的开始。此外,为了使延迟按照你的要求工作,你必须将每个动画包装在一个 CAAnimationGroup 中,并将其 duration 设置为整个动画的总时间。
使用 frame 属性也可能会导致减速。我真的很喜欢在这种情况下利用 CALayertransform 属性,比如进行“打开”动画。你可以在 IB 中(或者在代码中)以最终位置布置你的图像,在窗口变得可见之前,修改它们的转换到动画的起始位置。然后,你只需将所有的变换重置为 CATransform3DIdentity,即可将界面恢复到正常状态。
我在我的即将发布的 Core Animation 书籍中有一个例子,与你尝试做的非常相似。它可以同时动画化 30 个 NSImageView,不会丢帧。我为你修改了这个例子,并将其放在了 GitHub 上。这些是最相关的代码片段,剔除了多余的 UI 部分。

将图层转换到起始位置

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
  // ... SNIP ... //
  //Start with all of the images at the origin
  [CATransaction begin];
  [CATransaction setDisableActions:YES];
  for (CALayer *imageLayer in [[[self imageContainer] layer] sublayers]) {
    CGPoint layerPosition = [layer position];
    CATransform3D originTransform = CATransform3DMakeTranslation(20.f - layerPosition.x, -layerPosition.y, 0.f);
    [imageLayer setTransform:originTransform];
  }
  [CATransaction commit];
}

将变换动画返回到标识

- (IBAction)runAnimation:(id)sender {
  CALayer *containerLayer = [[self imageContainer] layer];

  NSTimeInterval delay = 0.f;
  NSTimeInterval delayStep = .05f;
  NSTimeInterval singleDuration = [[self durationStepper] doubleValue];
  NSTimeInterval fullDuration = singleDuration + (delayStep * [[containerLayer sublayers] count]);

  for (CALayer *imageLayer in [containerLayer sublayers]) {
    CATransform3D currentTransform = [[imageLayer presentationLayer] transform];

    CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"transform"];
    anim.beginTime = delay;
    anim.fromValue = [NSValue valueWithCATransform3D:currentTransform];
    anim.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
    anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    anim.fillMode = kCAFillModeBackwards;
    anim.duration = singleDuration;

    CAAnimationGroup *group = [CAAnimationGroup animation];
    group.animations = [NSArray arrayWithObject:anim];
    group.duration = fullDuration;

    [imageLayer setTransform:CATransform3DIdentity];
    [imageLayer addAnimation:group forKey:@"transform"];
    delay += delayStep;
  }
}

我还在YouTube上分享了一个示例的视频,如果你想查看的话。


这看起来非常有前途!可能需要我一点时间来阅读所有内容,但视频演示看起来很棒。 - Kevin Sylvestre

1

你尝试过在CATransaction中批处理所有内容吗?

[CATransaction begin];
for {...}
[CATransaction commit];

CATransaction是核心动画机制,用于将多个图层树操作批处理为对渲染树的原子更新。

似乎没有产生任何影响。 - Kevin Sylvestre

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接