【问题标题】:Cancel a UIView animation?取消 UIView 动画?
【发布时间】:2010-10-07 23:41:33
【问题描述】:

是否可以取消正在进行的UIView 动画?还是我必须降到 CA 级别?

即我做过这样的事情(也许也设置了结束动画动作):

[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:duration];
[UIView setAnimationCurve: UIViewAnimationCurveLinear];
// other animation properties

// set view properties

[UIView commitAnimations];

但是在动画完成并且我得到动画结束事件之前,我想取消它(缩短它)。这可能吗?谷歌搜索发现有几个人问同样的问题却没有答案 - 并且一两个人推测这是不可能的。

【问题讨论】:

  • 您的意思是在中间暂停动画并将其留在那里吗?还是回到开始的地方?
  • 要么把它留在原处,在动画中间(实际上我会在之后立即开始另一个动画),或者直接跳到终点。我想第一个更自然,但在这种情况下任何一个都对我有用。
  • @Chris Hanson:我很欣赏您的编辑,但想知道您删除 iPhone 标签的理由是什么。可可触摸可能暗示了这一点,但我在 iPhone 上有一个标签过滤器,在这种情况下会错过这个。
  • @Boot To The Head(再次):你的问题,以及我自己对它的回答,促使我进行了更多的孤立测试,我发现如果你在第一个动画之前开始一个新动画完成它确实会在开始新的之前跳转到终点。
  • - 这满足了我的迫切需要(并表明我的真实代码中还有其他错误),但我将保留这个问题,因为我知道其他人一直在问同样的问题。

标签: ios core-animation uiviewanimation


【解决方案1】:

所有已回答的解决方案都不适合我。我以这种方式解决了我的问题(我不知道这是否正确?),因为我在调用这个太快时遇到了问题(当之前的动画尚未完成时)。我通过 customAnim 块传递了我想要的动画。

extension UIView
{

    func niceCustomTranstion(
        duration: CGFloat = 0.3,
        options: UIView.AnimationOptions = .transitionCrossDissolve,
        customAnim: @escaping () -> Void
        )
    {
        UIView.transition(
            with: self,
            duration: TimeInterval(duration),
            options: options,
            animations: {
                customAnim()
        },
            completion: { (finished) in
                if !finished
                {
                    // NOTE: This fixes possible flickering ON FAST TAPPINGS
                    // NOTE: This fixes possible flickering ON FAST TAPPINGS
                    // NOTE: This fixes possible flickering ON FAST TAPPINGS
                    self.layer.removeAllAnimations()
                    customAnim()
                }
        })

    }

}

【讨论】:

    【解决方案2】:

    当我使用UIStackView 动画时,除了removeAllAnimations() 之外,我还需要将一些值设置为初始值,因为removeAllAnimations() 可以将它们设置为不可预测的状态。我有stackView,里面有view1view2,一个视图应该是可见的,一个是隐藏的:

    public func configureStackView(hideView1: Bool, hideView2: Bool) {
        let oldHideView1 = view1.isHidden
        let oldHideView2 = view2.isHidden
        view1.layer.removeAllAnimations()
        view2.layer.removeAllAnimations()
        view.layer.removeAllAnimations()
        stackView.layer.removeAllAnimations()
        // after stopping animation the values are unpredictable, so set values to old
        view1.isHidden = oldHideView1 //    <- Solution is here
        view2.isHidden = oldHideView2 //    <- Solution is here
    
        UIView.animate(withDuration: 0.3,
                       delay: 0.0,
                       usingSpringWithDamping: 0.9,
                       initialSpringVelocity: 1,
                       options: [],
                       animations: {
                        view1.isHidden = hideView1
                        view2.isHidden = hideView2
                        stackView.layoutIfNeeded()
        },
                       completion: nil)
    }
    

    【讨论】:

      【解决方案3】:

      Stephen Darlington 解决方案的 Swift 版本

      UIView.beginAnimations(nil, context: nil)
      UIView.setAnimationBeginsFromCurrentState(true)
      UIView.setAnimationDuration(0.1)
      // other animation properties
      
      // set view properties
      UIView.commitAnimations()
      

      【讨论】:

        【解决方案4】:

        很抱歉恢复这个答案,但在 iOS 10 中,情况发生了一些变化,现在可以取消,您甚至可以优雅地取消!

        在 iOS 10 之后,您可以使用 UIViewPropertyAnimator 取消动画!

        UIViewPropertyAnimator(duration: 2, dampingRatio: 0.4, animations: {
            view.backgroundColor = .blue
        })
        animator.stopAnimation(true)
        

        如果你传递 true,它会取消动画并在你取消它的地方停止。不会调用完成方法。但是,如果您传递 false,您有责任完成动画:

        animator.finishAnimation(.start)
        

        您可以完成动画并保持当前状态 (.current) 或转到初始状态 (.start) 或结束状态 (.end)

        顺便说一句,您甚至可以稍后暂停并重新启动...

        animator.pauseAnimation()
        animator.startAnimation()
        

        注意:如果您不想突然取消,您可以反转动画,甚至在暂停后更改动画!

        【讨论】:

        • 这绝对是最适合现代动画的。
        【解决方案5】:

        暂停动画而不将状态恢复到原始或最终状态:

        CFTimeInterval paused_time = [myView.layer convertTime:CACurrentMediaTime() fromLayer:nil];
        myView.layer.speed = 0.0;
        myView.layer.timeOffset = paused_time;
        

        【讨论】:

          【解决方案6】:

          如果你只是想流畅地暂停/停止动画

          self.yourView.layer.speed = 0;
          

          来源:How to pause the animation of a layer tree

          【讨论】:

            【解决方案7】:
            [UIView setAnimationsEnabled:NO];
            // your code here
            [UIView setAnimationsEnabled:YES];
            

            【讨论】:

              【解决方案8】:

              如果您通过更改常量而不是视图属性来为约束设置动画,则其他方法都不能在 iOS 8 上运行。

              示例动画:

              self.constraint.constant = 0;
              [self.view updateConstraintsIfNeeded];
              [self.view layoutIfNeeded];
              [UIView animateWithDuration:1.0f
                                    delay:0.0f
                                  options:UIViewAnimationOptionCurveLinear
                               animations:^{
                                   self.constraint.constant = 1.0f;
                                   [self.view layoutIfNeeded];
                               } completion:^(BOOL finished) {
              
                               }];
              

              解决方案:

              您需要从受约束更改影响的任何视图的图层及其子图层中删除动画。

              [self.constraintView.layer removeAllAnimations];
              for (CALayer *l in self.constraintView.layer.sublayers)
              {
                  [l removeAllAnimations];
              }
              

              【讨论】:

              • 也只有 self.constraintView.layer.removeAllAnimations() 有效(Swift)
              • 其他解决方案都不起作用。谢谢,它对我有用。
              【解决方案9】:

              在 iOS 4 及更高版本上,在第二个动画上使用UIViewAnimationOptionBeginFromCurrentState 选项来缩短第一个动画。

              例如,假设您有一个带有活动指示器的视图。您希望在一些可能耗时的活动开始时淡入活动指示器,并在活动完成时淡出。在下面的代码中,上面带有活动指示器的视图称为activityView

              - (void)showActivityIndicator {
                  activityView.alpha = 0.0;
                  activityView.hidden = NO;
                  [UIView animateWithDuration:0.5
                               animations:^(void) {
                                   activityView.alpha = 1.0;
                               }];
              
              - (void)hideActivityIndicator {
                  [UIView animateWithDuration:0.5
                               delay:0 options:UIViewAnimationOptionBeginFromCurrentState
                               animations:^(void) {
                                   activityView.alpha = 0.0;
                               }
                               completion:^(BOOL completed) {
                                   if (completed) {
                                       activityView.hidden = YES;
                                   }
                               }];
              }
              

              【讨论】:

                【解决方案10】:
                CALayer * pLayer = self.layer.presentationLayer;
                [UIView setAnimationBeginsFromCurrentState:YES];
                [UIView animateWithDuration:0.001 animations:^{
                    self.frame = pLayer.frame;
                }];
                

                【讨论】:

                  【解决方案11】:

                  以上都没有为我解决这个问题,但这有帮助:UIView 动画立即设置属性,然后对其进行动画处理。当表示层匹配模型(设置属性)时,它会停止动画。

                  我解决了我的问题,即“我想从你看起来像你出现的地方开始制作动画”(“你”是指视图)。如果你想要那个,那么:

                  1. 添加 QuartzCore。
                  2. CALayer * pLayer = theView.layer.presentationLayer;

                  设置展示层的位置

                  我使用了一些选项,包括UIViewAnimationOptionOverrideInheritedDuration

                  但是因为Apple的文档很模糊,我不知道它在使用时是否真的会覆盖其他动画,或者只是重置计时器。

                  [UIView animateWithDuration:blah... 
                                      options: UIViewAnimationOptionBeginFromCurrentState ... 
                                      animations: ^ {
                                                     theView.center = CGPointMake( pLayer.position.x + YOUR_ANIMATION_OFFSET, pLayer.position.y + ANOTHER_ANIMATION_OFFSET);
                                     //this only works for translating, but you get the idea if you wanna flip and scale it. 
                                     } completion: ^(BOOL complete) {}];
                  

                  目前这应该是一个不错的解决方案。

                  【讨论】:

                    【解决方案12】:

                    即使您以上述方式取消动画,动画didStopSelector 仍然会运行。因此,如果您的应用程序中有由动画驱动的逻辑状态,您就会遇到问题。出于这个原因,通过上述方式,我使用UIView 动画的上下文变量。如果您通过上下文参数将程序的当前状态传递给动画,则当动画停止时,您的didStopSelector 函数可能会根据当前状态和作为上下文传递的状态值决定它是应该做某事还是仅仅返回。

                    【讨论】:

                    • NickForge 在上面 TomTaylor 正确答案下方的评论中完美地解释了这一点......
                    • 非常感谢您的留言!我确实有这样的情况,当调用 animationDidStop 时会触发下一个动画。如果您只是删除一个动画并立即为同一个键重新添加一个新动画,我发现它不起作用。您需要等待,例如,直到 animationDidStop 触发另一个动画或其他动画。刚刚移除的动画将不再触发 animationDidStop。
                    【解决方案13】:

                    在特定视图立即停止所有动画的最简单方法是:

                    将项目链接到 QuartzCore.framework。在代码的开头:

                    #import <QuartzCore/QuartzCore.h>
                    

                    现在,当您想停止视图中的所有动画时,可以这样说:

                    [CATransaction begin];
                    [theView.layer removeAllAnimations];
                    [CATransaction commit];
                    

                    中间线可以自己工作,但是在运行循环完成之前会有延迟(“重绘时刻”)。为了防止这种延迟,请将命令包装在显式事务块中,如图所示。前提是在当前运行循环中没有对该层执行其他更改。

                    【讨论】:

                    • 我一直在使用 [CATransaction flush] 你的代码效果很好,有时会导致它不会立即写入。但是,有一个 0.1 秒的性能问题,例如它滞后于那个特定的时间。有什么解决方法吗?
                    • removeAllAnimations 的副作用是将动画带到最后一帧,而不是在当前位置停止。
                    • 通常,当一个动画想要停止时,他们希望它在当前帧停止——不是第一帧也不是最后一帧。跳到第一帧或最后一帧看起来像不平稳的动作。
                    • 我会详细说明答案,然后指定默认行为是什么以及如何更改它。显然,做动画的人对他的方法调用后屏幕上发生的事情非常感兴趣:)
                    • 我已经在别处详述了。这不是被问到的,所以这不是我在这里回答的。如果您还有其他答案,请务必将其作为答案!
                    【解决方案14】:

                    用途:

                    #import <QuartzCore/QuartzCore.h>
                    
                    .......
                    
                    [myView.layer removeAllAnimations];
                    

                    【讨论】:

                    • 我发现我必须添加 #import 才能删除编译器警告;)
                    • 如此简单,却又如此难找。谢谢你,我刚刚花了一个小时试图解决这个问题。
                    • 这个解决方案的可能问题是(至少在 iOS 5 中)在它生效之前有一个延迟 - 如果你的意思是“现在停止动画,之前我继续下一行代码”。
                    • 我发现如果使用 + (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:( void (^)(BOOL 完成))完成
                    • @matt 你知道立即停止动画的解决方案吗?在动画真正停止之前,我也观察到了一点延迟。
                    【解决方案15】:

                    要取消动画,您只需在 UIView 动画之外设置当前正在动画的属性。这将在任何地方停止动画,并且 UIView 将跳转到您刚刚定义的设置。

                    【讨论】:

                    • 这只是部分正确。它确实会停止动画,但不会阻止它触发委托方法,最值得注意的是,animationComplete 处理程序,所以如果你在那里有逻辑,你会看到问题。
                    • 这就是-animationDidStop:finished:context: 中的“完成”参数的用途。如果[finished boolValue] == NO,那么你知道你的动画被某种方式取消了。
                    • 这是 7 年前的一个很好的提示!请注意,有一个奇怪的行为:假设您正在前后设置 alpha 动画,然后将其设置为“转到 0”。要停止动画,您不能将 alpha 设置为零;您将其设置为 1。
                    【解决方案16】:

                    我这样做的方式是为您的终点创建一个新动画。设置一个非常短的持续时间并确保使用+setAnimationBeginsFromCurrentState: 方法从当前状态开始。当您将其设置为 YES 时,当前动画将被剪短。看起来像这样:

                    [UIView beginAnimations:nil context:NULL];
                    [UIView setAnimationBeginsFromCurrentState:YES];
                    [UIView setAnimationDuration:0.1];
                    [UIView setAnimationCurve: UIViewAnimationCurveLinear];
                    // other animation properties
                    
                    // set view properties
                    
                    [UIView commitAnimations];
                    

                    【讨论】:

                    • 谢谢斯蒂芬。实际上,如果您阅读了我的问题下的 cmets,这正是我最终所做的 - 但是由于您在这里给出了一个很好、清晰的说明,我将其标记为已接受
                    • @Vojto 怎么样?文档说 iOS 4 不鼓励使用 setAnimationBeginsFromCurrentState:,但它仍然存在并且应该仍然有效。
                    • 在 iOS4+ 中,在 animateWithDuration:delay:options:animations:completion: 上使用 UIViewAnimationOptionBeginFromCurrentState 选项:
                    • UIViewAnimationOptionBeginFromCurrentState 会取消任何正在进行的动画还是仅取消针对同一属性的动画?如果是任何动画,如何让它在同一点继续新动画而不停止正在进行的同一动画?
                    • @yourfriendzak,根据一个简短的测试,看起来它只是取消了针对同一属性的动画。我尝试更改 contentOffset 动画并且 contentOffset 动画继续的 scrollView 的 alpha。
                    【解决方案17】:

                    我也有同样的问题; API 没有任何东西可以取消某些特定的动画。

                    + (void)setAnimationsEnabled:(BOOL)enabled

                    禁用所有动画,因此对我不起作用。有两种解决方案:

                    1) 使您的动画对象成为子视图。然后,当您想取消该视图的动画时,删除该视图或将其隐藏。非常简单,但是如果需要保持在视图中,则需要重新创建不带动画的子视图。

                    2) 只重复一个动画,并在需要时制作一个委托选择器来重新启动动画,如下所示:

                    -(void) startAnimation {
                    NSLog(@"startAnim alpha:%f", self.alpha);
                    [self setAlpha:1.0];
                    [UIView beginAnimations:nil context:nil];
                    [UIView setAnimationDuration:1.0];
                    [UIView setAnimationRepeatCount:1];
                    [UIView setAnimationRepeatAutoreverses:YES];
                    [UIView setAnimationDelegate:self];
                    [UIView setAnimationDidStopSelector:@selector(pulseAnimationDidStop:finished:context:)];
                    [self setAlpha:0.1];
                    [UIView commitAnimations];
                    }
                    
                    - (void)pulseAnimationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
                    if(hasFocus) {
                        [self startAnimation];
                    } else {
                        self.alpha = 1.0;
                    }
                    }
                    
                    -(void) setHasFocus:(BOOL)_hasFocus {
                    hasFocus = _hasFocus;
                    if(hasFocus) {
                        [self startAnimation];
                    }
                    }
                    

                    2) 的问题是,当动画完成当前动画周期时,总是会延迟停止动画。

                    希望这会有所帮助。

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2023-03-19
                      相关资源
                      最近更新 更多