【问题标题】:how to create iphone's wobbling icon effect?如何创建iphone的摆动图标效果?
【发布时间】:2009-05-30 08:43:15
【问题描述】:

我想在我的应用程序中来回摆动图像,类似于按下 iPhone 图标时的摆动方式。最好的方法是什么?

这是我第一次尝试不使用动画 GIF 的动画。我认为这个想法是稍微来回旋转图像以产生摆动效果。我看过使用 CABasicAnimation 和 CAKeyframeAnimation。 CABasicAnimation 每次重复时都会产生抖动,因为它会跳转到起始位置并且不会向后插值。 CAKeyframeAnimation 似乎是解决方案,但我无法让它工作。我肯定错过了什么。这是我使用 CAKeyframeAnimation 的代码(不起作用):

    NSString *keypath = @"wobbleImage";
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:keypath];
animation.duration = 1.0f;
animation.delegate = self;
animation.repeatCount = 5;

CGFloat wobbleAngle = 0.0872664626f;
NSValue *initial = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(0.0f, 0.0f, 0.0f, 1.0f)];
NSValue *middle = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(wobbleAngle, 0.0f, 0.0f, 1.0f)];
NSValue *final = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(-wobbleAngle, 0.0f, 0.0f, 1.0f)];
animation.values = [NSArray arrayWithObjects:initial, middle, final, nil];

[imageView.layer addAnimation:animation forKey:keypath];


或者可能有一个我只是想念的完全简单的解决方案。感谢任何指针。谢谢!

【问题讨论】:

    标签: iphone ios animation rotation


    【解决方案1】:

    简单的方法:

    #define RADIANS(degrees) (((degrees) * M_PI) / 180.0)
    
    CGAffineTransform leftWobble = CGAffineTransformRotate(CGAffineTransformIdentity, RADIANS(-5.0));
    CGAffineTransform rightWobble = CGAffineTransformRotate(CGAffineTransformIdentity, RADIANS(5.0));
    
    itemView.transform = leftWobble;  // starting point
    
    [UIView beginAnimations:@"wobble" context:itemView];
    [UIView setAnimationRepeatAutoreverses:YES]; // important
    [UIView setAnimationRepeatCount:10];
    [UIView setAnimationDuration:0.25];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:@selector(wobbleEnded:finished:context:)];
    
    itemView.transform = rightWobble; // end here & auto-reverse
    
    [UIView commitAnimations];
    
    ...
    
    - (void) wobbleEnded:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context 
    {
         if ([finished boolValue]) {
            UIView* item = (UIView *)context;
            item.transform = CGAffineTransformIdentity;
         }
    }
    

    可能需要考虑时间和角度,但这应该可以帮助您入门。

    编辑:我编辑了响应以添加代码以在完成后将项目恢复为原始状态。另外,请注意,您可以使用beginAnimations 上下文值将任何内容传递给启动/停止方法。在这种情况下,它是摆动对象本身,因此您不必依赖特定的 ivars,并且该方法可用于任何基于 UIView 的通用对象(即文本标签、图像等)

    【讨论】:

    • 这很好用,但是当我希望它以原始状态结束时,图像最终处于旋转状态。你知道如何在动画块本身中做到这一点吗?我必须使用 animationDidStopSelector 来重置位置吗?
    • 不错!我还没有尝试过,但看起来视图是否会以旋转变换突然开始,并从旋转变换突然结束到恒等变换(= 无旋转)。所以你需要更多的代码来让它正确,即制作 3 个阶段。阶段 1)将动画旋转到 leftWobble,阶段 2)做动画,阶段 3)将动画旋转回身份变换。
    • 谢谢!这非常适合我的需要。如果要进行更平滑的摆动,这 3 个阶段看起来会起作用,但使用 CAKeyframeAnimation 是否有意义?
    • 是的。对于更复杂的多阶段动画——尤其是在有时间限制的情况下——核心动画比 UIView 动画链更容易管理和维护。 UIView 更适合“设置并忘记”类型的操作,尤其是在简单的移动、缩放和旋转方面。它还可以方便地随时间更改视图属性,例如 alpha 和颜色值。
    • 这很有帮助。问题:有没有办法让这个动画无限运行直到用户点击视图?
    【解决方案2】:

    Ramin 的回答非常好,但是从 OS4 开始,在一个简单的函数中使用 animateWithDuration 也可以实现相同的效果。

    (为未来的谷歌员工改编了他的例子)

    #define RADIANS(degrees) (((degrees) * M_PI) / 180.0)
    
    - (void)startWobble {
     itemView.transform = CGAffineTransformRotate(CGAffineTransformIdentity, RADIANS(-5));
    
     [UIView animateWithDuration:0.25 
          delay:0.0 
          options:(UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse)
          animations:^ {
           itemView.transform = CGAffineTransformRotate(CGAffineTransformIdentity, RADIANS(5));
          }
          completion:NULL
     ];
    }
    
    - (void)stopWobble {
     [UIView animateWithDuration:0.25
          delay:0.0 
          options:(UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveLinear)
          animations:^ {
           itemView.transform = CGAffineTransformIdentity;
          }
          completion:NULL
      ];
    }
    

    【讨论】:

    • 为我工作,因为我正在寻找基于块的解决方案。
    • 我认为这是更简单、更干净的方法,谢谢!
    • 关闭模态视图后如何重新启动动画?
    • 非常好。我发现更小的角度(3 或 4 度)和更短的动画时间(0.1 秒)更接近于主屏幕动画。
    【解决方案3】:

    您应该使用CAKeyframeAnimation 来制作更流畅的动画。

    + (void) animationKeyFramed: (CALayer *) layer 
                       delegate: (id) object
                  forKey: (NSString *) key {
    
        CAKeyframeAnimation *animation;
        animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation.z"];
        animation.duration = 0.4;
        animation.cumulative = YES;
        animation.repeatCount = 2;
        animation.values = [NSArray arrayWithObjects:
                [NSNumber numberWithFloat: 0.0], 
                [NSNumber numberWithFloat: RADIANS(-9.0)], 
                [NSNumber numberWithFloat: 0.0],
                [NSNumber numberWithFloat: RADIANS(9.0)],
                [NSNumber numberWithFloat: 0.0], nil];
        animation.fillMode = kCAFillModeForwards;
        animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
        animation.removedOnCompletion = NO;
        animation.delegate = object;
    
        [layer addAnimation:animation forKey:key];
    }
    

    【讨论】:

      【解决方案4】:

      我编写了一个示例应用程序,尝试复制主屏幕摆动和图标移动:iPhone Sample Code: Tiles

      【讨论】:

      • 我很欣赏这个代码示例还包括 Y 方向的平移;这是其他人的解决方案中缺少的微妙效果。
      • 帮助很大。谢谢:)
      【解决方案5】:

      对于最近看到这篇文章并希望在 Swift 中做同样事情的任何人,这是我的翻译:

      func smoothJiggle() {
      
          let degrees: CGFloat = 5.0
          let animation = CAKeyframeAnimation(keyPath: "transform.rotation.z")
          animation.duration = 0.6
          animation.cumulative = true
          animation.repeatCount = Float.infinity
          animation.values = [0.0,
              degreesToRadians(-degrees) * 0.25,
              0.0,
              degreesToRadians(degrees) * 0.5,
              0.0,
              degreesToRadians(-degrees),
              0.0,
              degreesToRadians(degrees),
              0.0,
              degreesToRadians(-degrees) * 0.5,
              0.0,
              degreesToRadians(degrees) * 0.25,
              0.0]
          animation.fillMode = kCAFillModeForwards;
          animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
          animation.removedOnCompletion = true
      
          layer.addAnimation(animation, forKey: "wobble")
      }
      
      func stopJiggling() {
          jiggling = false
          self.layer.removeAllAnimations()
          self.transform = CGAffineTransformIdentity
          self.layer.anchorPoint = CGPointMake(0.5, 0.5)
      }
      

      【讨论】:

        【解决方案6】:

        我知道的最简单的方法是使用 Core Animation。基本上,您创建一个核心动画块,然后进行旋转变换并设置和重复计数。然后,Core Animation 会处理完成这种摆动效果所需的一切。

        要启动核心动画块,只需:

        [UIView beginAnimations:@"any string as animationID" context:self];
        [UIView setAnimationRepeatCount:10];
        // rotate 
        [UIView commitAnimations];
        

        未测试。但也有可能您还必须这样做:

        [UIView setAnimationBeginsFromCurrentState:YES];
        

        A.F.A.I.K. setAnimationRepeatCount 将产生动画完成、撤消、完成、撤消、完成、撤消、完成...的效果,次数与您指定的一样多。因此,您可能希望先向左旋转而没有重复计数,然后从这一点开始重复计数摆动。完成后,您可能希望旋转回恒等变换(= 未应用旋转和缩放)。

        您可以通过设置动画委托来链接动画

        [UIView setAnimationDelegate:self]
        

        然后

        [UIView setAnimationDidStopSelector:@selector(myMethod:finished:context:)];
        

        一旦动画停止,该方法就会被调用。有关如何实现将在动画停止时调用的方法,请参阅 UIView 类文档。基本上,在该方法中,您将使用新的动画块但相同的上下文和动画 ID 执行下一步(即旋转回或其他任何操作),然后(如果需要)指定另一个 didStopSelector。

        更新:

        您可能想签出:

        [UIView setAnimationRepeatAutoreverses:YES];
        

        这会自动来回摆动。

        【讨论】:

        • 谢谢谢谢!我可以在我的函数中一个接一个地设置动画块链而不使用 DidStopSelector 吗?是否有任何最佳做法或性能注意事项?
        • 没有。我认为您需要使用 DidStopSelector。不幸的是,这在代码中看起来有点不方便,因为您必须为每个动态阶段创建一个方法。但是,如果您的动画由“向左摆动”、“向中心摆动”和“向右摆动”等简单阶段组成,您可以重复使用它们。如果您不希望有多个方法,您可以将方法本身指定为 didStopSelector。但这会使事情变得更加复杂。我通常会一个接一个地设置一系列方法来链接它们。完成后,将 didStopSelector 设置为 nil。
        • 更新:见 [UIView setAnimationRepeatAutoreverses:YES];
        【解决方案7】:

        您可以使用CAKeyframeAnimation 来创建一个非无尽的摆动效果,如下所示:

        CGFloat degrees = 8.0;
        CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation.z"];
        animation.duration = 0.6;
        animation.cumulative = YES;
        animation.repeatCount = 1;
        animation.values = @[@0.0,
                            @RADIANS(-degrees) * 0.25,
                            @0.0,
                            @RADIANS(degrees) * 0.5,
                            @0.0,
                            @RADIANS(-degrees),
                            @0.0,
                            @RADIANS(degrees),
                            @0.0,
                            @RADIANS(-degrees) * 0.5,
                            @0.0,
                            @RADIANS(degrees) * 0.25,
                            @0.0];
        animation.fillMode = kCAFillModeForwards;
        animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
        animation.removedOnCompletion = YES;
        
        [self.layer addAnimation:animation forKey:@"wobble"];
        

        【讨论】:

        • 或者你可以使用 Just [animation setRepeatCount:INFINITY];
        【解决方案8】:

        从上面的答案,我得到了 swift5 版本:

          func startWobble() {
            let angle = 5.0 * Double.pi / 180.0;
            self.transform = CGAffineTransform.identity.rotated(by: CGFloat(-angle));
            UIView.animate(withDuration: 0.25, delay: 0, options: [.allowUserInteraction,.repeat,.autoreverse], animations: {
                 self.transform = CGAffineTransform.identity.rotated(by: CGFloat(angle));
            }, completion: nil)
        
        }
        
        func stopWobble() {
        
            UIView.animate(withDuration: 0.25, delay: 0, options: [.allowUserInteraction,.beginFromCurrentState,.curveLinear], animations: {
                self.transform = CGAffineTransform.identity;
            }, completion: nil)
        }
        

        【讨论】:

          【解决方案9】:

          聚会迟到了。我通常使用带阻尼的弹簧(iOS7 和更新版本)。很快它看起来像:

              sender.transform = CGAffineTransformMakeScale(1.2, 1.2)
              UIView.animateWithDuration(0.30, delay: 0.0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.3, options: UIViewAnimationOptions.CurveEaseInOut, animations: { () -> Void in
                  sender.transform = CGAffineTransformMakeScale(1, 1)
              }) { (Bool) -> Void in
              //Do stuff when animation is finished
              }
          

          您可以通过调整initialSpringVelocitySpringWithDamping 来调整效果

          【讨论】:

          • 这似乎没有摆动(旋转)。
          • 你是对的!如果你想让它旋转使用 CGAffineTransformMakeRotation 而不是 CGAffineTransformMakeScale
          • 我就是这么做的。感谢您的帮助!
          【解决方案10】:

          基于 EsbenB 的回答,但针对 Swift 3 和轮换进行了更新:

              sender.transform = CGAffineTransform(rotationAngle: 12.0 * .pi / 180.0)
              UIView.animate(withDuration: 0.60, delay: 0.0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.3, options: .curveEaseInOut, animations: { () -> Void in
                  sender.transform = CGAffineTransform(rotationAngle: 0.0)
              }, completion: nil)
          

          【讨论】:

            【解决方案11】:

            Ramin 给出的代码运行良好。但是如果您使用标签栏应用程序并移动到下一个标签项目然后再次返回到上一个标签项目,您会看到您的视图每次都向左移动。所以最好的做法是你使用 ViewWillAppear 方法。

            - (void)viewWillAppear:(BOOL)animated
            {
                UIView* item = self.view;
                item.transform = CGAffineTransformIdentity;
            }
            

            所以每次加载视图时,您都会在正确的位置找到动画。并且也使用此方法。

            [UIView setAnimationDidStopSelector:@selector(myMethod:finished:context:)];
            

            【讨论】:

              【解决方案12】:

              斯威夫特 3

              func startWiggling() {
                  deleteButton.isHidden = false
                  guard contentView.layer.animation(forKey: "wiggle") == nil else { return }
                  guard contentView.layer.animation(forKey: "bounce") == nil else { return }
              
                  let angle = 0.04
              
                  let wiggle = CAKeyframeAnimation(keyPath: "transform.rotation.z")
                  wiggle.values = [-angle, angle]
                  wiggle.autoreverses = true
                  wiggle.duration = randomInterval(0.1, variance: 0.025)
                  wiggle.repeatCount = Float.infinity
                  contentView.layer.add(wiggle, forKey: "wiggle")
              
                  let bounce = CAKeyframeAnimation(keyPath: "transform.translation.y")
                  bounce.values = [4.0, 0.0]
                  bounce.autoreverses = true
                  bounce.duration = randomInterval(0.12, variance: 0.025)
                  bounce.repeatCount = Float.infinity
                  contentView.layer.add(bounce, forKey: "bounce")
              }
              
              func stopWiggling() {
                  deleteButton.isHidden = true
                  contentView.layer.removeAllAnimations()
              }
              

              【讨论】:

                【解决方案13】:

                使用 Swift 4+ 最简单的方法:

                static func wobbleAnimation(button: UIButton) {
                    CATransaction.begin()
                    let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
                    rotateAnimation.autoreverses = true
                    rotateAnimation.repeatCount = Float.greatestFiniteMagnitude
                    rotateAnimation.fromValue = CGFloat(-0.2)
                    rotateAnimation.toValue = CGFloat(0.2)
                    rotateAnimation.duration = 0.20
                    button.layer.add(rotateAnimation, forKey: nil)
                    CATransaction.commit()
                }
                
                static func stopWobbleAnimation(button: UIButton) {
                    button.layer.removeAllAnimations()
                }
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2011-01-08
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多