动画完成后 CABasicAnimation 重置为初始值

发布于 2024-11-08 14:08:04 字数 125 浏览 0 评论 0原文

我正在旋转 CALayer 并尝试在动画完成后将其停止在最终位置。

但动画完成后,它会重置到初始位置。

(xcode 文档明确指出动画不会更新属性的值。)

有关如何实现此目的的任何建议。

I am rotating a CALayer and trying to stop it at its final position after animation is completed.

But after animation completes it resets to its initial position.

(xcode docs explicitly say that the animation will not update the value of the property.)

any suggestions how to achieve this.

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

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

发布评论

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

评论(15

星光不落少年眉 2024-11-15 14:08:05

returnedOnCompletion 的问题是 UI 元素不允许用户交互。

我的技术是在动画中设置 FROM 值,在对象上设置 TO 值。
动画将在开始之前自动填充 TO 值,并且当它被删除时将使对象保持正确的状态。

// fade in
CABasicAnimation *alphaAnimation = [CABasicAnimation animationWithKeyPath: @"opacity"];
alphaAnimation.fillMode = kCAFillModeForwards;

alphaAnimation.fromValue = NUM_FLOAT(0);
self.view.layer.opacity = 1;

[self.view.layer addAnimation: alphaAnimation forKey: @"fade"];

The problem with removedOnCompletion is the UI element does not allow user interaction.

I technique is to set the FROM value in the animation and the TO value on the object.
The animation will auto fill the TO value before it starts, and when it's removed will leave the object at it's correct state.

// fade in
CABasicAnimation *alphaAnimation = [CABasicAnimation animationWithKeyPath: @"opacity"];
alphaAnimation.fillMode = kCAFillModeForwards;

alphaAnimation.fromValue = NUM_FLOAT(0);
self.view.layer.opacity = 1;

[self.view.layer addAnimation: alphaAnimation forKey: @"fade"];
匿名的好友 2024-11-15 14:08:05

核心动画维护两层层次结构:模型层表示层。当动画进行时,模型层实际上是完整的并保持其初始值。默认情况下,动画一旦完成就会被删除。然后表示层回落到模型层的值。

只需将 removedOnCompletion 设置为 NO 就意味着动画不会被删除并浪费内存。此外,模型层和表示层将不再同步,这可能会导致潜在的错误。

所以直接将模型层的属性更新为最终值会是一个更好的解决方案。

self.view.layer.opacity = 1;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
animation.fromValue = 0;
animation.toValue = 1;
[self.view.layer addAnimation:animation forKey:nil];

如果上面代码第一行引起了隐式动画,请尝试关闭if:

[CATransaction begin];
[CATransaction setDisableActions:YES];
self.view.layer.opacity = 1;
[CATransaction commit];

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
animation.fromValue = 0;
animation.toValue = 1;
[self.view.layer addAnimation:animation forKey:nil];

Core animation maintains two layer hierarchies: the model layer and the presentation layer. When the animation is in progress, the model layer is actually intact and keeps it initial value. By default, the animation is removed once the it's completed. Then the presentation layer falls back to the value of the model layer.

Simply setting removedOnCompletion to NO means the animation won't be removed and wastes memory. In addition, the model layer and the presentation layer won't be synchronous any more, which may lead to potential bugs.

So it would be a better solution to update the property directly on the model layer to the final value.

self.view.layer.opacity = 1;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
animation.fromValue = 0;
animation.toValue = 1;
[self.view.layer addAnimation:animation forKey:nil];

If there's any implicit animation caused by the first line of above code, try to turn if off:

[CATransaction begin];
[CATransaction setDisableActions:YES];
self.view.layer.opacity = 1;
[CATransaction commit];

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
animation.fromValue = 0;
animation.toValue = 1;
[self.view.layer addAnimation:animation forKey:nil];
三生一梦 2024-11-15 14:08:05

设置以下属性:

animationObject.removedOnCompletion = NO;

Set the following property:

animationObject.removedOnCompletion = NO;
那小子欠揍 2024-11-15 14:08:05

当您将CABasicAnimation添加到图层时,您只需将其关键点设置为position即可。通过这样做,它将覆盖在运行循环中当前通道的位置上完成的隐式动画。

CGFloat yOffset = 30;
CGPoint endPosition = CGPointMake(someLayer.position.x,someLayer.position.y + yOffset);

someLayer.position = endPosition; // Implicit animation for position

CABasicAnimation * animation =[CABasicAnimation animationWithKeyPath:@"position.y"]; 

animation.fromValue = @(someLayer.position.y);
animation.toValue = @(someLayer.position.y + yOffset);

[someLayer addAnimation:animation forKey:@"position"]; // The explicit animation 'animation' override implicit animation

您可以了解更多有关 2011 Apple WWDC 视频会议 421 - 核心动画要点(视频中间)的信息

You can simply set the key of CABasicAnimation to position when you add it to the layer. By doing this, it will override implicit animation done on the position for the current pass in the run loop.

CGFloat yOffset = 30;
CGPoint endPosition = CGPointMake(someLayer.position.x,someLayer.position.y + yOffset);

someLayer.position = endPosition; // Implicit animation for position

CABasicAnimation * animation =[CABasicAnimation animationWithKeyPath:@"position.y"]; 

animation.fromValue = @(someLayer.position.y);
animation.toValue = @(someLayer.position.y + yOffset);

[someLayer addAnimation:animation forKey:@"position"]; // The explicit animation 'animation' override implicit animation

You can have more information on 2011 Apple WWDC Video Session 421 - Core Animation Essentials (middle of the video)

海拔太高太耀眼 2024-11-15 14:08:05

这是可行的:

let animation = CABasicAnimation(keyPath: "opacity")
animation.fromValue = 0
animation.toValue = 1
animation.duration = 0.3

someLayer.opacity = 1 // important, this is the state you want visible after the animation finishes.
someLayer.addAnimation(animation, forKey: "myAnimation")

核心动画在动画过程中在普通层之上显示一个“表示层”。因此,将不透明度(或其他)设置为您希望在动画完成且表示层消失时看到的内容。在添加动画之前执行此操作,以避免动画完成时出现闪烁。

如果您想要延迟,请执行以下操作:

let animation = CABasicAnimation(keyPath: "opacity")
animation.fromValue = 0
animation.toValue = 1
animation.duration = 0.3
animation.beginTime = someLayer.convertTime(CACurrentMediaTime(), fromLayer: nil) + 1
animation.fillMode = kCAFillModeBackwards // So the opacity is 0 while the animation waits to start.

someLayer.opacity = 1 // <- important, this is the state you want visible after the animation finishes.
someLayer.addAnimation(animation, forKey: "myAnimation")

最后,如果您使用“removedOnCompletion = false”,它将泄漏 CAAnimations,直到该层最终被处置 - 避免。

This works:

let animation = CABasicAnimation(keyPath: "opacity")
animation.fromValue = 0
animation.toValue = 1
animation.duration = 0.3

someLayer.opacity = 1 // important, this is the state you want visible after the animation finishes.
someLayer.addAnimation(animation, forKey: "myAnimation")

Core animation shows a 'presentation layer' atop your normal layer during the animation. So set the opacity (or whatever) to what you want to be seen when the animation finishes and the presentation layer goes away. Do this on the line before you add the animation to avoid a flicker when it completes.

If you want to have a delay, do the following:

let animation = CABasicAnimation(keyPath: "opacity")
animation.fromValue = 0
animation.toValue = 1
animation.duration = 0.3
animation.beginTime = someLayer.convertTime(CACurrentMediaTime(), fromLayer: nil) + 1
animation.fillMode = kCAFillModeBackwards // So the opacity is 0 while the animation waits to start.

someLayer.opacity = 1 // <- important, this is the state you want visible after the animation finishes.
someLayer.addAnimation(animation, forKey: "myAnimation")

Finally, if you use 'removedOnCompletion = false' it'll leak CAAnimations until the layer is eventually disposed - avoid.

毁梦 2024-11-15 14:08:05

只需将其放入您的代码中即可

CAAnimationGroup *theGroup = [CAAnimationGroup animation];

theGroup.fillMode = kCAFillModeForwards;

theGroup.removedOnCompletion = NO;

just put it inside your code

CAAnimationGroup *theGroup = [CAAnimationGroup animation];

theGroup.fillMode = kCAFillModeForwards;

theGroup.removedOnCompletion = NO;
々眼睛长脚气 2024-11-15 14:08:05

CALayer 具有模型层和表示层。在动画期间,表示层独立于模型进行更新。动画完成后,表示层将使用模型中的值进行更新。如果您想避免动画结束后出现不和谐的跳跃,关键是保持两层同步。

如果知道最终值,则可以直接设置模型。

self.view.layer.opacity = 1;

但是,如果您有一个不知道结束位置的动画(例如,用户可以暂停然后反转的缓慢淡入淡出),那么您可以直接查询表示层以查找当前值,然后更新模型。

NSNumber *opacity = [self.layer.presentationLayer valueForKeyPath:@"opacity"];
[self.layer setValue:opacity forKeyPath:@"opacity"];

从表示层提取值对于缩放或旋转键路径也特别有用。 (例如transform.scaletransform.rotation

A CALayer has a model layer and a presentation layer. During an animation, the presentation layer updates independently of the model. When the animation is complete, the presentation layer is updated with the value from the model. If you want to avoid a jarring jump after the animation ends, the key is to keep the two layers in sync.

If you know the end value, you can just set the model directly.

self.view.layer.opacity = 1;

But if you have an animation where you don't know the end position (e.g. a slow fade that the user can pause and then reverse), then you can query the presentation layer directly to find the current value, and then update the model.

NSNumber *opacity = [self.layer.presentationLayer valueForKeyPath:@"opacity"];
[self.layer setValue:opacity forKeyPath:@"opacity"];

Pulling the value from the presentation layer is also particularly useful for scaling or rotation keypaths. (e.g. transform.scale, transform.rotation)

秋心╮凉 2024-11-15 14:08:05

所以我的问题是我试图通过平移手势旋转一个对象,所以每次移动时我都会有多个相同的动画。我有 fillMode = kCAFillModeForwardsisRemovedOnCompletion = false 但它没有帮助。就我而言,我必须确保每次添加新动画时动画关键点都不同

let angle = // here is my computed angle
let rotate = CABasicAnimation(keyPath: "transform.rotation.z")
rotate.toValue = angle
rotate.duration = 0.1
rotate.isRemovedOnCompletion = false
rotate.fillMode = CAMediaTimingFillMode.forwards

head.layer.add(rotate, forKey: "rotate\(angle)")

So my problem was that I was trying to rotate an object on pan gesture and so I had multiple identical animations on each move. I had both fillMode = kCAFillModeForwards and isRemovedOnCompletion = false but it didn't help. In my case, I had to make sure that the animation key is different each time I add a new animation:

let angle = // here is my computed angle
let rotate = CABasicAnimation(keyPath: "transform.rotation.z")
rotate.toValue = angle
rotate.duration = 0.1
rotate.isRemovedOnCompletion = false
rotate.fillMode = CAMediaTimingFillMode.forwards

head.layer.add(rotate, forKey: "rotate\(angle)")
浊酒尽余欢 2024-11-15 14:08:05

在不使用 removedOnCompletion 的情况下,

您可以尝试以下技术:

self.animateOnX(item: shapeLayer)

func animateOnX(item:CAShapeLayer)
{
    let endPostion = CGPoint(x: 200, y: 0)
    let pathAnimation = CABasicAnimation(keyPath: "position")
    //
    pathAnimation.duration = 20
    pathAnimation.fromValue = CGPoint(x: 0, y: 0)//comment this line and notice the difference
    pathAnimation.toValue =  endPostion
    pathAnimation.fillMode = kCAFillModeBoth

    item.position = endPostion//prevent the CABasicAnimation from resetting item's position when the animation finishes

    item.add(pathAnimation, forKey: nil)
}

Without using the removedOnCompletion

You can try this technique:

self.animateOnX(item: shapeLayer)

func animateOnX(item:CAShapeLayer)
{
    let endPostion = CGPoint(x: 200, y: 0)
    let pathAnimation = CABasicAnimation(keyPath: "position")
    //
    pathAnimation.duration = 20
    pathAnimation.fromValue = CGPoint(x: 0, y: 0)//comment this line and notice the difference
    pathAnimation.toValue =  endPostion
    pathAnimation.fillMode = kCAFillModeBoth

    item.position = endPostion//prevent the CABasicAnimation from resetting item's position when the animation finishes

    item.add(pathAnimation, forKey: nil)
}
带上头具痛哭 2024-11-15 14:08:05

简单地设置 fillModeremovedOnCompletion 对我来说不起作用。我通过将下面的所有属性设置为 CABasicAnimation 对象解决了这个问题:

CABasicAnimation* ba = [CABasicAnimation animationWithKeyPath:@"transform"];
ba.duration = 0.38f;
ba.fillMode = kCAFillModeForwards;
ba.removedOnCompletion = NO;
ba.autoreverses = NO;
ba.repeatCount = 0;
ba.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.85f, 0.85f, 1.0f)];
[myView.layer addAnimation:ba forKey:nil];

此代码将 myView 转换为其大小的 85%(第三维不变)。

Simply setting fillMode and removedOnCompletion didn't work for me. I solved the problem by setting all of the properties below to the CABasicAnimation object:

CABasicAnimation* ba = [CABasicAnimation animationWithKeyPath:@"transform"];
ba.duration = 0.38f;
ba.fillMode = kCAFillModeForwards;
ba.removedOnCompletion = NO;
ba.autoreverses = NO;
ba.repeatCount = 0;
ba.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.85f, 0.85f, 1.0f)];
[myView.layer addAnimation:ba forKey:nil];

This code transforms myView to 85% of its size (3rd dimension unaltered).

筱武穆 2024-11-15 14:08:05

@Leslie Godwin 的回答不太好,“self.view.layer.opacity = 1;”立即完成(大约需要一秒),如果您有疑问,请将 alphaAnimation.duration 修复为 10.0。
你必须删除这一行。

因此,当您将 fillMode 固定为 kCAFillModeForwards 并将removedOnCompletion 固定为 NO 时,您可以让动画保留在图层中。如果您修复动画委托并尝试以下操作:

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
 [theLayer removeAllAnimations];
}

...执行此行时图层会立即恢复。这是我们想要避免的。

您必须先修复图层属性,然后才能从中删除动画。试试这个:

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
     if([anim isKindOfClass:[CABasicAnimation class] ]) // check, because of the cast
    {
        CALayer *theLayer = 0;
        if(anim==[_b1 animationForKey:@"opacity"])
            theLayer = _b1; // I have two layers
        else
        if(anim==[_b2 animationForKey:@"opacity"])
            theLayer = _b2;

        if(theLayer)
        {
            CGFloat toValue = [((CABasicAnimation*)anim).toValue floatValue];
            [theLayer setOpacity:toValue];

            [theLayer removeAllAnimations];
        }
    }
}

@Leslie Godwin's answer is not really good, "self.view.layer.opacity = 1;" is done immediately (it takes about one second), please fix alphaAnimation.duration to 10.0, if you have doubts.
You have to remove this line.

So, when you fix fillMode to kCAFillModeForwards and removedOnCompletion to NO, you let the animation remains in the layer. If you fix the animation delegate and try something like:

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
 [theLayer removeAllAnimations];
}

...the layer restores immediately at the moment you execute this line. It's what we wanted to avoid.

You must fix the layer property before remove the animation from it. Try this:

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
     if([anim isKindOfClass:[CABasicAnimation class] ]) // check, because of the cast
    {
        CALayer *theLayer = 0;
        if(anim==[_b1 animationForKey:@"opacity"])
            theLayer = _b1; // I have two layers
        else
        if(anim==[_b2 animationForKey:@"opacity"])
            theLayer = _b2;

        if(theLayer)
        {
            CGFloat toValue = [((CABasicAnimation*)anim).toValue floatValue];
            [theLayer setOpacity:toValue];

            [theLayer removeAllAnimations];
        }
    }
}
可是我不能没有你 2024-11-15 14:08:05

最简单的解决方案是使用隐式动画。这将为您处理所有这些麻烦:

self.layer?.backgroundColor = NSColor.red.cgColor;

如果您想自定义例如持续时间,您可以使用 NSAnimationContext :

    NSAnimationContext.beginGrouping();
    NSAnimationContext.current.duration = 0.5;
    self.layer?.backgroundColor = NSColor.red.cgColor;
    NSAnimationContext.endGrouping();

注意:这仅在 macOS 上进行了测试。

我最初在执行此操作时没有看到任何动画。问题是视图支持的图层隐式动画。要解决此问题,请确保您自己添加一个图层(在将视图设置为图层支持之前)。

如何执行此操作的一个示例是:

override func awakeFromNib() {
    self.layer = CALayer();
    //self.wantsLayer = true;
}

使用 self.wantsLayer 在我的测试中没有产生任何差异,但它可能会产生一些我不知道的副作用。

The easiest solution is to use implicit animations. This will handle all of that trouble for you:

self.layer?.backgroundColor = NSColor.red.cgColor;

If you want to customize e.g. the duration, you can use NSAnimationContext:

    NSAnimationContext.beginGrouping();
    NSAnimationContext.current.duration = 0.5;
    self.layer?.backgroundColor = NSColor.red.cgColor;
    NSAnimationContext.endGrouping();

Note: This is only tested on macOS.

I initially did not see any animation when doing this. The problem is that the layer of a view-backed layer does not implicit animate. To solve this, make sure you add a layer yourself (before setting the view to layer-backed).

An example how to do this would be:

override func awakeFromNib() {
    self.layer = CALayer();
    //self.wantsLayer = true;
}

Using self.wantsLayer did not make any difference in my testing, but it could have some side effects that I do not know of.

酒几许 2024-11-15 14:08:05

似乎将removedOnCompletion 标志设置为 false 并将 fillMode 设置为 kCAFillModeForwards 对我来说也不起作用。

在图层上应用新动画后,动画对象将重置为其初始状态,然后从该状态开始动画。
另外还需要做的是在设置新动画之前根据其表示层的属性设置模型层所需的属性,如下所示:

someLayer.path = ((CAShapeLayer *)[someLayer presentationLayer]).path;
[someLayer addAnimation:someAnimation forKey:@"someAnimation"];

It seems that removedOnCompletion flag set to false and fillMode set to kCAFillModeForwards doesn't work for me either.

After I apply new animation on a layer, an animating object resets to its initial state and then animates from that state.
What has to be done additionally is to set the model layer's desired property according to its presentation layer's property before setting new animation like so:

someLayer.path = ((CAShapeLayer *)[someLayer presentationLayer]).path;
[someLayer addAnimation:someAnimation forKey:@"someAnimation"];
蹲墙角沉默 2024-11-15 14:08:05

这是来自 Playground 的示例:

import PlaygroundSupport
import UIKit

let resultRotation = CGFloat.pi / 2
let view = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 200.0, height: 300.0))
view.backgroundColor = .red

//--------------------------------------------------------------------------------

let rotate = CABasicAnimation(keyPath: "transform.rotation.z") // 1
rotate.fromValue = CGFloat.pi / 3 // 2
rotate.toValue = resultRotation // 3
rotate.duration = 5.0 // 4
rotate.beginTime = CACurrentMediaTime() + 1.0 // 5
// rotate.isRemovedOnCompletion = false // 6
rotate.fillMode = .backwards // 7

view.layer.add(rotate, forKey: nil) // 8
view.layer.setAffineTransform(CGAffineTransform(rotationAngle: resultRotation)) // 9

//--------------------------------------------------------------------------------

PlaygroundPage.current.liveView = view
  1. 创建动画模型
  2. 设置动画的开始位置(可以跳过,这取决于您当前的图层布局)
  3. 设置动画的结束位置
  4. 设置动画持续时间
  5. 将动画延迟一秒
  6. 不要将 false 设置为 isRemovedOnCompletion - 让核心动画在动画完成后清理
  7. 这是技巧的第一部分< /em> - 您对 Core Animation 说,在动画开始之前将动画放置到开始位置(您在步骤 #2 中设置) - 及时向后延伸
  8. 复制准备好的动画对象,将其添加到图层并开始延迟后的动画(您在步骤 #5 中设置)
  9. 第二部分是设置图层的正确结束位置 - 删除动画后,将显示您的图层在正确的地方

Here is a sample from playground:

import PlaygroundSupport
import UIKit

let resultRotation = CGFloat.pi / 2
let view = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 200.0, height: 300.0))
view.backgroundColor = .red

//--------------------------------------------------------------------------------

let rotate = CABasicAnimation(keyPath: "transform.rotation.z") // 1
rotate.fromValue = CGFloat.pi / 3 // 2
rotate.toValue = resultRotation // 3
rotate.duration = 5.0 // 4
rotate.beginTime = CACurrentMediaTime() + 1.0 // 5
// rotate.isRemovedOnCompletion = false // 6
rotate.fillMode = .backwards // 7

view.layer.add(rotate, forKey: nil) // 8
view.layer.setAffineTransform(CGAffineTransform(rotationAngle: resultRotation)) // 9

//--------------------------------------------------------------------------------

PlaygroundPage.current.liveView = view
  1. Create an animation model
  2. Set start position of the animation (could be skipped, it depends on your current layer layout)
  3. Set end position of the animation
  4. Set animation duration
  5. Delay animation for a second
  6. Do not set false to isRemovedOnCompletion - let Core Animation clean after the animation is finished
  7. Here is the first part of the trick - you say to Core Animation to place your animation to the start position (you set in step #2) before the animation has even been started - extend it backwards in time
  8. Copy prepared animation object, add it to the layer and start the animation after the delay (you set in step #5)
  9. The second part is to set correct end position of the layer - after the animation is deleted your layer will be shown at the correct place
染墨丶若流云 2024-11-15 14:08:04

这就是答案,它是我的答案和克里希南的答案的结合。

cabasicanimation.fillMode = kCAFillModeForwards;
cabasicanimation.removedOnCompletion = NO;

默认值为kCAFillModeRemoved。 (这是您看到的重置行为。)

Here's the answer, it's a combination of my answer and Krishnan's.

cabasicanimation.fillMode = kCAFillModeForwards;
cabasicanimation.removedOnCompletion = NO;

The default value is kCAFillModeRemoved. (Which is the reset behavior you're seeing.)

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