使用 CABasicAnimation 回调的替代方案?

发布于 2024-10-20 23:57:15 字数 2821 浏览 2 评论 0原文

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

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

blip 代表的对象由 spriteControl 预取并组织到不可见的 CAShapeLayers 中。我使用 CABasicAnimation 将魔杖一次旋转 10 度,然后利用“animationDidStop:”方法调用 spriteControl 上的一个方法,该方法获取魔杖层的当前旋转值(也称为标题)并为 alpha 设置设置动画1.0 到 0.0 用于模拟闪入和淡出效果。最后,该过程无限期地重新开始。

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

我可以生成一个 NSTimer 来触发一个方法,该方法查询魔杖表示层的角度以获取标题值。然而,这使得保持魔杖和光点突出显示同步变得更加困难,和/或导致一些被完全跳过。这里讨论了这种方法: 我如何回调CABasicAnimation 正在制作动画?

所以我的问题是,我是否可以采取任何措施来提高魔杖层旋转的性能,而无需使用 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];
}

CAAnimation does not provide a mechanism for assigning callback functions other than the standard "animationDidStart:"/"animationDidStop:" methods.

I have a custom UIControl that utilizes 2 CALayers that overlap. The purpose of this control is similar to an old fashioned sonar. The top layer's contents contains an image that gets rotated constantly (call this layer "wand"). Beneath that layer is a "spriteControl" layer that renders blips as the wand passes over them.

The objects that the blips represent are pre-fetched and organized into invisible CAShapeLayers by the spriteControl. I am using a CABasicAnimation to rotate the wand 10 degrees at a time, then utilizing the "animationDidStop:" method to invoke a method on the spriteControl that takes the current rotation value of the wand layer (a.k.a. heading) and animates the alpha setting from 1.0 to 0.0 for simulating the blip in and fade out effect. Finally, the process is started over again indefinitely.

While this approach of using the CAAnimation callbacks ensures that the timing of the wand reaching a "ping" position (i.e. 10deg, 20deg, 270deg, etc) always coincide with the lighting of the blips in the other layer, there is this issue of stopping, recalculating, and starting the animation every 10 degrees.

I could spawn an NSTimer to fire a method that queries the angle of the wand's presentation layer to get the heading value. However, this makes it more difficult to keep the wand and the blip highlighting in sync, and/or cause some to get skipped altogether. This approach is discussed a bit here: How can I callback as a CABasicAnimation is animating?

So my question is whether or not there is anything I can do to improve the performance of the wand layer rotation without reimplementing the control using OpenGL ES. (I realize that this would be easily solved in an OpenGL environment, however, to use it here would require extensive redesign that simply isn't worth it.) While the performance issue is minor, I can't shake the feeling that there is something simple and obvious that I could do that would allow the wand to animate indefinitely without pausing to perform expensive rotation calculations in between.

Here is some code:

- (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];
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

缺⑴份安定 2024-10-27 23:57:15

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

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

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

Let's say it takes 10 seconds for the radar to make one complete rotation.

To get the wand to rotate indefinitely, attach a CABasicAnimation to it with its Duration property set to 10 and its RepeatCount property set to 1e100f.

The blips can each be animated using their own instance CAKeyframeAnimation. I won't write the details, but for each blip, you specify an array of opacity values (I assume opacity is how you're fading out the blips) and an array of time percentages (see Apple's documentation).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文