UIView animateKeyframesWithDuration与标准动画块(standard animation block)动画的区别

9

最近我发现了一些 UIView 动画代码,并且注意到它没有正确使用 animateKeyframesWithDuration:delay:options:animations:completion: 方法。自那以后,我一直在尝试修复它,但由于某种原因,动画并不完全相同。

这是我找到的代码:

view.transform = CGAffineTransformMakeTranslation(300, 0);

[UIView animateKeyframesWithDuration:duration/4 delay:delay options:0 animations:^{
    // End
    view.transform = CGAffineTransformMakeTranslation(-10, 0);
} completion:^(BOOL finished) {
    [UIView animateKeyframesWithDuration:duration/4 delay:0 options:0 animations:^{
        // End
        view.transform = CGAffineTransformMakeTranslation(5, 0);
    } completion:^(BOOL finished) {
        [UIView animateKeyframesWithDuration:duration/4 delay:0 options:0 animations:^{
            // End
            view.transform = CGAffineTransformMakeTranslation(-2, 0);
        } completion:^(BOOL finished) {
            [UIView animateKeyframesWithDuration:duration/4 delay:0 options:0 animations:^{
                // End
                view.transform = CGAffineTransformMakeTranslation(0, 0);
            } completion:^(BOOL finished) {
            }];
        }];
    }];
}];

这里有两个问题:
  1. 应该使用关键帧动画来代替在完成块内嵌套动画。
  2. 当使用 animateKeyframesWithDuration:delay:options:animations:completion: 方法时,必须在动画块内调用 addKeyframeWithRelativeStartTime:relativeDuration:animations: 方法。如果不调用此方法,则该方法将像标准的 UIView 动画块一样运行。

在这里查看苹果官方文档中有关该方法的介绍。

我试图通过正确地利用这个方法来修复这个问题:
view.transform = CGAffineTransformMakeTranslation(300, 0);
double frameDuration = 1.0/4.0; // 4 = number of keyframes

[UIView animateKeyframesWithDuration:duration delay:delay options:0 animations:^{
    [UIView addKeyframeWithRelativeStartTime:0*frameDuration relativeDuration:frameDuration animations:^{
        view.transform = CGAffineTransformMakeTranslation(-10, 0);
    }];
    [UIView addKeyframeWithRelativeStartTime:1*frameDuration relativeDuration:frameDuration animations:^{
        view.transform = CGAffineTransformMakeTranslation(5, 0);
    }];
    [UIView addKeyframeWithRelativeStartTime:2*frameDuration relativeDuration:frameDuration animations:^{
        view.transform = CGAffineTransformMakeTranslation(-2, 0);
    }];
    [UIView addKeyframeWithRelativeStartTime:3*frameDuration relativeDuration:frameDuration animations:^{
        view.transform = CGAffineTransformMakeTranslation(0, 0);
    }];
} completion:nil];

我认为这两个动画应该是相同的,但实际上它们并不相同。我的尝试错在哪里了?
编辑:示例项目(使用 Command + T 切换模拟器中的慢速动画):链接

然而,它们并不相同。你能详细说明一下吗?它们有什么不同之处? - David Rönnqvist
非常抱歉,我本应该添加一个示例项目,现在已经添加了。关键帧的时间不匹配。另外:哇,David Rönnqvist!你的清除*动画代码文章是我发布这个问题的原因! - iMaddin
1个回答

17
tl;dr: 它们看起来可能不同是因为它们执行不同的任务,并且导致两个视图添加了不同的动画。不幸的是,我不知道如何修复它。

第一个版本背后的发生了什么

正如你已经说过的那样,第一个版本没有正确使用 API。它没有使用 addKeyframeWithRelativeStartTime:relativeDuration:animations: 来添加关键帧,而是直接在动画块内更改属性。然后它在完成块中创建了一个新的“关键帧动画”(您很快就会看到为什么我在这里使用担心引号)。

在幕后,这实际上并没有导致 CAKeyframeAnimations(尽管我认为您不应该依赖于这一事实)。这是我对您的项目中 view1 后面添加的四个动画对象进行的一些日志记录。(我稍微作弊了一点,只记录了变换的 .m41 部分(它对应于沿 x 轴的平移))。

BASIC
keyPath: transform
duration: 0.125
from: 300.0
model value: -10.0
timing: easeInEaseOut

BASIC
keyPath: transform
duration: 0.125
from: -10.0
model value: 5.0
timing: easeInEaseOut

BASIC
keyPath: transform
duration: 0.125
from: 5.0
model value: -2.0
timing: easeInEaseOut

BASIC
keyPath: transform
duration: 0.125
from: -2.0
model value: 0.0
timing: easeInEaseOut

正如您所看到的,每个动画都有一个fromValue,但没有toValuebyValue。根据文档,这意味着:

fromValue是非nil的。在fromValue和属性的当前呈现值之间进行插值。


第二个版本背后发生了什么

另一方面,正确向动画添加关键帧的第二个版本会导致此单个CAKeyframeAnimation添加到view2后面的图层中。(我仍然只记录变换的.m41部分)
KEYFRAME 
keyPath: transform
duration: 0.500
values: (
    300,
    -10,
    5,
    -2,
    0
)
keyTimes: (
    0.0,
    0.25,
    0.5,
    0.75,
    1.0
)
timing: easeInEaseOut
timings: (nil)
calculationMode: linear

正如您所看到的,它在单个关键帧动画中以相同的时间间隔和值之间进行动画。它还具有相同的总持续时间并使用相同的计时函数。

但是,有一件事情我不太理解。如果我修改真正的关键帧动画(在调用超类之前覆盖 addAnimation:forKey: 方法),在计时函数的数组中明确设置计时函数,那么它们看起来完全相同。也就是说,在将其添加到图层之前,我对关键帧动画进行了更改。

CAMediaTimingFunction *function = keyFrame.timingFunction;
keyFrame.timingFunction = nil;
keyFrame.timingFunctions = @[function, function, function, function]; 

这个技巧非常丑陋,除了纯粹的调试之外,你永远不应该在其他方面使用它!


那么为什么它们看起来不同呢?

嗯,我想答案与UIKit在第二种情况下创建的关键帧动画的时间函数数组有关。那几乎是我得出的唯一结论。

话虽如此,我不知道这是否是一个错误,也不知道如何修复它。我只知道你项目中的那段代码在核心动画层面上实际上做了什么。


3
实际上,UIKit 没有设置 timingFunctions,它只设置了 timingFunction。截至 iOS 9,UIKit 方面没有提供相关的 API。目前,我创建了这个小扩展,允许在动画之后调整 timingFunctiontimingFunctions:https://github.com/salutis/View-KeyframeAnimationCurve。 - Rudolf Adamkovič
你是如何记录这些动画描述的?我需要调试我的动画并获得类似的输出。 - GRiMe2D

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