【问题标题】:CABasicAnimation resets to initial value after animation completesCABasicAnimation 在动画完成后重置为初始值
【发布时间】:2011-08-28 21:06:23
【问题描述】:

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

但是在动画完成后它会重置到它的初始位置。

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

任何建议如何实现这一点。

【问题讨论】:

  • 这是一个奇怪的 SO 问题,其中几乎所有的答案都是完全错误的 - 完全错误。您非常简单地查看.presentation() 以获得“最终的,看到的”值。向下搜索下面的正确答案,解释它是通过表示层完成的。
  • 此线程上的最佳答案是:stackoverflow.com/a/50668490/1101099 - 最佳答案可能“有效”,但不是推荐的解决方案。确保在复制/粘贴之前了解 Core Animation 编程模型! developer.apple.com/library/archive/documentation/Cocoa/…

标签: ios iphone calayer cabasicanimation


【解决方案1】:

这是答案,它是我的答案和 Krishnan 的结合。

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

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

【讨论】:

  • 这可能不是正确的答案 - 请参阅对 Krishnan 答案的评论。你通常需要这个和克里希南的;只有一个或另一个不起作用。
  • 这不是正确答案,请参阅下面@Leslie Godwin 的答案。
  • @Leslie 解决方案更好,因为它将最终值存储在图层中而不是动画中。
  • 在将 removedOnCompletion 设置为 NO 时要小心。如果您将动画委托分配给“self”,则动画将保留您的实例。因此,在调用 removeFromSuperview 后,如果您忘记自己删除/销毁动画,则不会调用例如 dealloc。你会有内存泄漏。
  • 这个 200+ 点的答案是完全、完全、完全不正确的。
【解决方案2】:

removedOnCompletion 的问题是 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"];

【讨论】:

  • 如果您设置了启动延迟,则不起作用。例如alphaAnimation.beginTime = 1; 另外,据我所知,不需要fillMode...?
  • 否则,这是一个很好的答案,接受的答案不会让您在动画完成后更改值...
  • 我还应该注意beginTime不是相对的,你应该使用例如:CACurrentMediaTime()+1;
  • 这是“它的”而不是“它的”——its-not-its.info。对不起,如果这只是一个错字。
【解决方案3】:

设置以下属性:

animationObject.removedOnCompletion = NO;

【讨论】:

  • @Nilesh:接受你的回答。这将使其他 SO 用户对您的回答充满信心。
  • 我必须同时使用 .fillMode = kCAFillModeForwards 和 .removedOnCompletion = NO。谢谢!
【解决方案4】:

添加到图层时,您可以简单地将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 Video Session 421 - Core Animation Essentials(视频中间)了解更多信息

【讨论】:

  • 这看起来是正确的做法。动画自然会被移除(默认),一旦动画完成,它会返回到动画开始之前设置的值,特别是这一行:someLayer.position = endPosition;。并感谢 WWDC 的参考!
【解决方案5】:

核心动画维护两个层级:the model layerthe presentation layer。当动画进行时,模型层实际上是完整的并保持初始值。默认情况下,动画一旦完成就会被移除。然后表现层回退到模型层的值。

简单地将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];

如果上面第一行代码有隐式动画,如果关闭,尝试关闭:

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

【讨论】:

  • 谢谢!这确实应该是公认的答案,因为它正确解释了此功能,而不仅仅是使用创可贴来使其工作
  • 这是我在这个线程上找到的最佳答案,因为它基于对核心动画如何在幕后工作的真正理解回答了这个问题。这并不是说接受的答案是“错误的”,但它不是推荐的第一个解决方案。
【解决方案6】:

只需将其放入您的代码中

CAAnimationGroup *theGroup = [CAAnimationGroup animation];

theGroup.fillMode = kCAFillModeForwards;

theGroup.removedOnCompletion = NO;

【讨论】:

    【解决方案7】:

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

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

    self.view.layer.opacity = 1;
    

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

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

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

    【讨论】:

      【解决方案8】:

      所以我的问题是我试图在平移手势上旋转一个对象,所以每次移动都有多个相同的动画。我同时拥有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)")
      

      【讨论】:

        【解决方案9】:

        这行得通:

        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,直到最终释放层 - 避免。

        【讨论】:

        • 很好的答案,很好的提示。感谢您的removedOnCompletion 评论。
        • 很好的答案,一行节省了我的时间
        【解决方案10】:

        不使用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)
        }
        

        【讨论】:

        • 这似乎是最好的答案
        • 同意。请注意@ayman 的评论,即您必须将 .fromValue 属性定义为起始值。否则,您将覆盖模型中的起始值,并且会有动画。
        • 这个和@jason-moore 的答案比公认的答案要好。在接受的答案中,动画只是暂停,但新值并未实际设置为属性。这可能会导致不良行为。
        【解决方案11】:

        简单地设置fillModeremovedOnCompletion 对我不起作用。我通过将 all 以下属性设置为 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%(第 3 维不变)。

        【讨论】:

          【解决方案12】:

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

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

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

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

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

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

          【讨论】:

            【解决方案13】:

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

            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 对我的测试没有任何影响,但它可能会产生一些我不知道的副作用。

            【讨论】:

              【解决方案14】:

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

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

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

              【讨论】:

                【解决方案15】:

                这是来自游乐场的示例:

                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 - 动画完成后让 Core Animation 清理
                7. 这是技巧的第一部分 - 你告诉 Core Animation 在动画开始之前将你的动画放置到开始位置(你在步骤 #2 中设置) - 扩展它时光倒流
                8. 复制准备好的动画对象,将其添加到图层并在延迟后启动动画(您在步骤#5中设置)
                9. 第二部分是设置正确的结束位置图层 - 删除动画后,您的图层将显示在正确的位置

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2013-03-22
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2021-05-31
                  相关资源
                  最近更新 更多