替代使用 CABasicAnimation 回调?

Posted

技术标签:

【中文标题】替代使用 CABasicAnimation 回调?【英文标题】:Alternative to using CABasicAnimation callbacks? 【发布时间】:2011-03-07 01:33:53 【问题描述】:

除了标准的“animationDidStart:”/“animationDidStop:”方法之外,CAAnimation 不提供分配回调函数的机制。

我有一个自定义 UIControl,它使用了 2 个重叠的 CALayers。这种控制的目的类似于老式的声纳。顶层的内容包含一个不断旋转的图像(将此层称为“魔杖”)。在该层之下是一个“spriteControl”层,当魔杖经过它们时会呈现光点。

blips 所代表的对象由 spriteControl 预先获取并组织成不可见的 CAShapeLayers。我正在使用 CABasicAnimation 一次将魔杖旋转 10 度,然后利用“animationDidStop:”方法调用 spriteControl 上的一个方法,该方法采用魔杖层的当前旋转值(又名标题)并从1.0 到 0.0 用于模拟闪烁和淡出效果。最后,这个过程无限期地重新开始。

虽然这种使用 CAAnimation 回调的方法可确保魔杖到达“ping”位置(即 10 度、20 度、270 度等)的时间始终与另一层中光点的照明一致,但有这个每 10 度停止、重新计算和启动动画的问题。

我可以生成一个 NSTimer 来触发一个方法,该方法查询魔杖表示层的角度以获取航向值。然而,这使得保持魔杖和 blip 突出显示同步变得更加困难,和/或导致一些被完全跳过。这种方法在这里讨论了一下:How can I callback as a CABasicAnimation is animating?

所以我的问题是,在不使用 OpenGL ES 重新实现控件的情况下,我是否可以做些什么来提高魔杖层旋转的性能。 (我意识到这在 OpenGL 环境中很容易解决,但是,在这里使用它需要进行大量的重新设计,这根本不值得。)虽然性能问题很小,但我无法摆脱这种感觉我可以做一些简单而明显的事情,这将允许魔杖无限期地动画,而不会在两者之间暂停执行昂贵的旋转计算。

这里有一些代码:

- (void)rotateWandByIncrement

    if (wandShouldStop)
        return;

    CGFloat newRotationDegree = (wandRotationDegree + WAND_INCREMENT_DEGREES);
    if (newRotationDegree >= 360)
        newRotationDegree = 0;


    CATransform3D rotationTransform = CATransform3DMakeRotation(DEGREES_TO_RADIANS(newRotationDegree), 0, 0, 1);

    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"];
    animation.toValue = [NSValue valueWithCATransform3D:rotationTransform];
    animation.duration = WAND_INCREMENT_DURATION;
    animation.fillMode = kCAFillModeForwards;
    animation.removedOnCompletion = FALSE;
    animation.delegate = self;

    [wandLayer addAnimation:animation forKey:@"transform"];


- (void)animationDidStart:(CAAnimation *)theAnimation

    if (wandShouldStop)
        return;

    NSInteger prevWandRotationDegree = wandRotationDegree - WAND_INCREMENT_DEGREES;
    if (prevWandRotationDegree < 0)
        prevWandRotationDegree += 360;

    // Pulse the spriteControl
    [[self spriteControl] pulseRayAtHeading:prevWandRotationDegree];


- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag

    // update the rotation var
    wandRotationDegree += WAND_INCREMENT_DEGREES;
    if (wandRotationDegree >= 360)
        wandRotationDegree = 0;

    // This applies the rotation value to the model layer so that
    // subsequent animations start where the previous one left off
    CATransform3D rotationTransform = CATransform3DMakeRotation(DEGREES_TO_RADIANS(wandRotationDegree), 0, 0, 1);

    [CATransaction begin];
    [CATransaction setDisableActions:TRUE];
    [wandLayer setTransform:rotationTransform];
    [CATransaction commit];

    //[wandLayer removeAnimationForKey:@"transform"];
    [self rotateWandByIncrement];

【问题讨论】:

更新:我已经通过创建预先计算值的静态数组消除了所有不必要的计算。我现在向自己保证,我的性能瓶颈不是由昂贵的数学运算引起的。然后我在魔杖层上实现了关键帧动画,但仍然得到了一个不太平滑的旋转。我实际上发现在这种情况下切换到 UIView 动画而不是 CAAnimation 可以提供更流畅的动画。到目前为止,似乎从 CAAnimation 获得了任何真正的性能优势,您希望完全避免使用回调,而让 GPU 完成它的工作。 【参考方案1】:

假设雷达旋转一圈需要 10 秒。

要让魔杖无限旋转,请将CABasicAnimation 附加到它,并将其Duration 属性设置为10,并将其RepeatCount 属性设置为1e100f。

每个 blip 都可以使用它们自己的实例 CAKeyframeAnimation 进行动画处理。我不会写详细信息,但是对于每个 blip,您指定一组不透明度值(我假设不透明度是您淡出 blip 的方式)和一组时间百分比(请参阅 Apple 的文档)。

【讨论】:

很有趣,感谢您的提示!目前,淡入淡出效果是应用于每个独立运行的 CALayer blip 的动画。我的问题与魔杖更相关,因为我希望它通过的时间来触发各个 blip 图层上的动画。我考虑过设置一个包含 36 个动画的 CAAnimationGroup,每个动画都设置了一个延迟值,以便它们连续运行。我希望这可能会给我每个动画的 animationDidStop 回调。但是 Apple 的文档似乎表明您只能为整个组获得其中一个,而不是其中的每个动画。

以上是关于替代使用 CABasicAnimation 回调?的主要内容,如果未能解决你的问题,请参考以下文章

CALayer的动画结束回调?

CABasicAnimation使用总结

CAAnimation 在发布的 CALayer 上运行回调——Mac OS X

使用 UINavigationController 滑动边缘时 CABasicAnimation 重置

如何使用 CABasicAnimation 更改 Frame.size

通过 WCF 双工通道长期运行的回调合同 - 替代设计模式?