UIView animateKeyframesWithDuration vs标准动画块动画不同

时间:2022-09-06 21:15:44

I recently found some UIView animation code and noticed that it was not using the animateKeyframesWithDuration:delay:options:animations:completion: method correctly and have since been trying to fix it but for some reason the animations are not identical.

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

Here's the code that I found:

这是我找到的代码:

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) {
            }];
        }];
    }];
}];

There are two problems with this:

这有两个问题:

  1. One should really use a key frame animation instead of nesting animations inside the completion block.
  2. 应该使用关键帧动画而不是在完成块内嵌套动画。

  3. When using animateKeyframesWithDuration:delay:options:animations:completion: you are supposed to call the addKeyframeWithRelativeStartTime:relativeDuration:animations: method inside the animation block. If you don't call this method then this method will behave like a standard UIView animation block.
  4. 使用animateKeyframesWithDuration时:delay:options:animations:completion:你应该在动画块中调用addKeyframeWithRelativeStartTime:relativeDuration:animations:方法。如果不调用此方法,则此方法的行为类似于标准UIView动画块。

See Apple's documentation regarding this method here.

请参阅Apple有关此方法的文档。

So I tried to fix this by utilising the method correctly:

所以我尝试通过正确使用该方法来解决这个问题:

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

I feel like both animations should be identical, however they are not. What did I do wrong in my attempt?

我觉得两个动画应该是相同的,但它们不是。在我的尝试中我做错了什么?

Edit: Example project (Use Command + T to toggle slow animations in the simulator)

编辑:示例项目(使用Command + T切换模拟器中的慢动画)

1 个解决方案

#1


17  

tl;dr: They probably look different because they do different things and result in different animations being added to the two views. Unfortunately I don't really know how to fix it.

tl; dr:他们可能看起来不同,因为他们做了不同的事情,导致不同的动画被添加到两个视图中。不幸的是,我真的不知道如何解决它。

What's happening behind the first version

As you have already said the first version doesn't use the API correctly. It doesn't add keyframes using addKeyframeWithRelativeStartTime:relativeDuration:animations: but instead changes the properties directly inside the animation block. Then it creates a new "key frame animation" (you will soon see why I used scare quotes there) in the completion block.

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

Behind the scenes this doesn't actually result in CAKeyframeAnimations (though I don't think you should rely on that fact). This is some logging that I did of the four animation objects that are added to the layer behind view1 in your project. (I cheated a bit and only logged the .m41 part of the transform (which corresponds to translation along x)).

在幕后,这实际上并没有导致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

As you can see each animation has a fromValue but neither toValue or byValue. As you can read in the documentation, this means:

正如您所看到的,每个动画都有一个fromValue但没有toValue或byValue。正如您在文档中看到的那样,这意味着:

fromValue is non-nil. Interpolates between fromValue and the current presentation value of the property.

fromValue是非零的。在fromValue和属性的当前显示值之间进行插值。


What's happening behind the second version

On the other hand, the second version that correctly adds keyframes to the animation results in this single CAKeyframeAnimation to be added to the layer behind view2. (I'm still only logging the .m41 part of the transform)

另一方面,正确地将关键帧添加到动画的第二个版本导致将单个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

As you can see it animates between the same values with the same times but in a single key frame animation. It also has the same total duration and uses the same timing function.

正如您所看到的,它在相同的值之间以相同的时间动画,但在单个关键帧动画中。它也具有相同的总持续时间并使用相同的计时功能。

There is one thing that I'm not understanding. If I modify the real key frame animation (inside of my overridden addAnimation:forKey: before calling super) to explicitly set the the timing function in the array of timing functions, then they do look exactly the same. That is, I do this to the keyframe animation before it is added to the layer.

有一件事我不理解。如果我修改真正的关键帧动画(在我的重写的addAnimation里面:forKey:在调用super之前)来显式设置定时函数数组中的定时函数,那么它们看起来完全一样。也就是说,我在关键帧动画添加到图层之前执行此操作。

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

This trick is super ugly and you should never use it for things other than pure debugging!

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


So why do they look different?

Well, I guess the answer has to do with the timing functions array of the keyframe animation that UIKit created in the second case. That's pretty much the only conclusion I got.

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

That said, I don't know if this is a bug or how to fix it. I just know what that code in your project actually does on the Core Animation level.

也就是说,我不知道这是一个错误还是如何解决它。我只知道你的项目中的代码实际上在核心动画级别上做了什么。

#1


17  

tl;dr: They probably look different because they do different things and result in different animations being added to the two views. Unfortunately I don't really know how to fix it.

tl; dr:他们可能看起来不同,因为他们做了不同的事情,导致不同的动画被添加到两个视图中。不幸的是,我真的不知道如何解决它。

What's happening behind the first version

As you have already said the first version doesn't use the API correctly. It doesn't add keyframes using addKeyframeWithRelativeStartTime:relativeDuration:animations: but instead changes the properties directly inside the animation block. Then it creates a new "key frame animation" (you will soon see why I used scare quotes there) in the completion block.

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

Behind the scenes this doesn't actually result in CAKeyframeAnimations (though I don't think you should rely on that fact). This is some logging that I did of the four animation objects that are added to the layer behind view1 in your project. (I cheated a bit and only logged the .m41 part of the transform (which corresponds to translation along x)).

在幕后,这实际上并没有导致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

As you can see each animation has a fromValue but neither toValue or byValue. As you can read in the documentation, this means:

正如您所看到的,每个动画都有一个fromValue但没有toValue或byValue。正如您在文档中看到的那样,这意味着:

fromValue is non-nil. Interpolates between fromValue and the current presentation value of the property.

fromValue是非零的。在fromValue和属性的当前显示值之间进行插值。


What's happening behind the second version

On the other hand, the second version that correctly adds keyframes to the animation results in this single CAKeyframeAnimation to be added to the layer behind view2. (I'm still only logging the .m41 part of the transform)

另一方面,正确地将关键帧添加到动画的第二个版本导致将单个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

As you can see it animates between the same values with the same times but in a single key frame animation. It also has the same total duration and uses the same timing function.

正如您所看到的,它在相同的值之间以相同的时间动画,但在单个关键帧动画中。它也具有相同的总持续时间并使用相同的计时功能。

There is one thing that I'm not understanding. If I modify the real key frame animation (inside of my overridden addAnimation:forKey: before calling super) to explicitly set the the timing function in the array of timing functions, then they do look exactly the same. That is, I do this to the keyframe animation before it is added to the layer.

有一件事我不理解。如果我修改真正的关键帧动画(在我的重写的addAnimation里面:forKey:在调用super之前)来显式设置定时函数数组中的定时函数,那么它们看起来完全一样。也就是说,我在关键帧动画添加到图层之前执行此操作。

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

This trick is super ugly and you should never use it for things other than pure debugging!

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


So why do they look different?

Well, I guess the answer has to do with the timing functions array of the keyframe animation that UIKit created in the second case. That's pretty much the only conclusion I got.

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

That said, I don't know if this is a bug or how to fix it. I just know what that code in your project actually does on the Core Animation level.

也就是说,我不知道这是一个错误还是如何解决它。我只知道你的项目中的代码实际上在核心动画级别上做了什么。