【问题标题】:Shake visual effect on iPhone (NOT shaking the device)在 iPhone 上晃动视觉效果(不晃动设备)
【发布时间】:2009-10-27 17:22:46
【问题描述】:

登录失败时,我宁愿避免显示警报,因为它太短暂了。显示警报然后在登录屏幕的某处显示文本似乎是重复的。

所以我希望它在用户输入错误的用户 ID 和密码时以图形方式震动我的登录视图,就像 Mac 登录屏幕一样。

有人知道是否有办法解决这个问题,或者对我可以使用的其他效果有什么建议吗?

【问题讨论】:

标签: iphone core-animation


【解决方案1】:

我认为这是一个更有效的解决方案:

斯威夫特:

let anim = CAKeyframeAnimation( keyPath:"transform" )
anim.values = [
    NSValue( CATransform3D:CATransform3DMakeTranslation(-5, 0, 0 ) ),
    NSValue( CATransform3D:CATransform3DMakeTranslation( 5, 0, 0 ) )
]
anim.autoreverses = true
anim.repeatCount = 2
anim.duration = 7/100

viewToShake.layer.addAnimation( anim, forKey:nil )

对象-C:

CAKeyframeAnimation * anim = [ CAKeyframeAnimation animationWithKeyPath:@"transform" ] ;
anim.values = @[ 
    [ NSValue valueWithCATransform3D:CATransform3DMakeTranslation(-5.0f, 0.0f, 0.0f) ], 
    [ NSValue valueWithCATransform3D:CATransform3DMakeTranslation( 5.0f, 0.0f, 0.0f) ] 
] ;
anim.autoreverses = YES ;
anim.repeatCount = 2.0f ;
anim.duration = 0.07f ;

[ viewToShake.layer addAnimation:anim forKey:nil ] ;

仅创建一个动画对象,并且全部在 CoreAnimation 级别执行。

【讨论】:

    【解决方案2】:

    使用基于 iOS 4+ 块的 UIKit 动画(大致基于 jayccrown 的回答):

    - (void)shakeView:(UIView *)viewToShake
    {
        CGFloat t = 2.0;
        CGAffineTransform translateRight  = CGAffineTransformTranslate(CGAffineTransformIdentity, t, 0.0);
        CGAffineTransform translateLeft = CGAffineTransformTranslate(CGAffineTransformIdentity, -t, 0.0);
    
        viewToShake.transform = translateLeft;
    
        [UIView animateWithDuration:0.07 delay:0.0 options:UIViewAnimationOptionAutoreverse|UIViewAnimationOptionRepeat animations:^{
            [UIView setAnimationRepeatCount:2.0];
            viewToShake.transform = translateRight;
        } completion:^(BOOL finished) {
            if (finished) {
                [UIView animateWithDuration:0.05 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
                    viewToShake.transform = CGAffineTransformIdentity;
                } completion:NULL];
            }
        }];
    }
    

    【讨论】:

    • 这应该是问题的最佳答案,因为在 ARC 开启的情况下,上述所有答案都无法正常工作(无需修改或省略 view 参数)。使用积木!
    • 是的,这是更好的答案,不过你可能不想检查finished,否则你可能会得到一个奇怪的变换
    • 你可以只用一个自动反转动画来做到这一点——不需要完成块。 (我在下面发布了代码)
    • 这在我的情况下有一个怪癖,即同时动摇了另一个视图。 @nielsbot 下面的 CAKeyframeAnimation 答案非常适合我。
    【解决方案3】:

    我看过一些摆动动画并将其更改为上下左右晃动 t 像素的视图:

    - (void)earthquake:(UIView*)itemView
    {
        CGFloat t = 2.0;
    
        CGAffineTransform leftQuake  = CGAffineTransformTranslate(CGAffineTransformIdentity, t, -t);
        CGAffineTransform rightQuake = CGAffineTransformTranslate(CGAffineTransformIdentity, -t, t);
    
        itemView.transform = leftQuake;  // starting point
    
        [UIView beginAnimations:@"earthquake" context:itemView];
        [UIView setAnimationRepeatAutoreverses:YES]; // important
        [UIView setAnimationRepeatCount:5];
        [UIView setAnimationDuration:0.07];
        [UIView setAnimationDelegate:self];
        [UIView setAnimationDidStopSelector:@selector(earthquakeEnded:finished:context:)];
    
        itemView.transform = rightQuake; // end here & auto-reverse
    
        [UIView commitAnimations];
    }
    
    - (void)earthquakeEnded:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context 
    {
        if ([finished boolValue]) 
        {
            UIView* item = (UIView *)context;
            item.transform = CGAffineTransformIdentity;
        }
    }
    

    【讨论】:

      【解决方案4】:

      这是一个详细说明如何在 Cocoa 中执行此操作的教程。 iPhone 应该是相同的(或至少非常相似)。

      http://www.cimgf.com/2008/02/27/core-animation-tutorial-window-shake-effect/

      【讨论】:

        【解决方案5】:

        只需更改视图 center 属性的 X 坐标即可。如果你之前没有做过任何核心动画,那就直截了当。

        首先,向右开始一个动画,然后听它完成,然后再向左移动,依此类推。缩短时间以便“感觉正确”可能需要一段时间。

        - (void)animationFinishCallback:(NSString *)animationID finished:(BOOL)finished context:(void *)context
        {
          if ([animationID isEqualToString:@"MoveRight"]) {
            [UIView beginAnimations:@"MoveLeft" context:NULL];
            [UIView setAnimationDuration:1.0];
            [UIView setAnimationDelay: UIViewAnimationCurveEaseIn];
            [UIView setAnimationDelegate:self];
            [UIView setAnimationDidStopSelector:@selector(animationFinishCallback:finished:context:)];
        
            myView.center = CGRectMake(newX, newY);
            [UIView commitAnimations];
          }
        }
        

        【讨论】:

        • 您也可以创建一个动画并将其应用到视图的CALayer。例如,这可以让您使用CAKeyframeAnimation,它可以让您微调动画。对于这样的动画,您可能需要这种级别的灵活性。
        • 在下面查看我的答案——它使用单个 CoreAnimation 动画,动画完成时会自动删除......不需要回调或多个动画块
        【解决方案6】:

        这个 UIView 类别 sn-p 对我有用。它使用了 3 个应用于视图层的 CABasingAnimations。

        #import <UIKit/UIKit.h>
        #import <QuartzCore/QuartzCore.h>
        
        #define Y_OFFSET 2.0f
        #define X_OFFSET 2.0f
        #define ANGLE_OFFSET (M_PI_4*0.1f)
        
        @interface UIView (shakeAnimation)
        
        -(BOOL)isShakeAnimationRunning;
        -(void)startShakeAnimation;
        -(void)stopShakeAnimation;
        
        @end
        
        
        
        @implementation UIView (shakeAnimation)
        
        -(BOOL)isShakeAnimationRunning{
             return [self.layer animationForKey:@"shake_rotation"] != nil;
        }
        
        -(void)startShakeAnimation{
            CFTimeInterval offset=(double)arc4random()/(double)RAND_MAX;
            self.transform=CGAffineTransformRotate(self.transform, -ANGLE_OFFSET*0.5);
            self.transform=CGAffineTransformTranslate(self.transform, -X_OFFSET*0.5f, -Y_OFFSET*0.5f);
        
            CABasicAnimation *tAnim=[CABasicAnimation animationWithKeyPath:@"position.x"];
            tAnim.repeatCount=HUGE_VALF;
            tAnim.byValue=[NSNumber numberWithFloat:X_OFFSET];
            tAnim.duration=0.07f;
            tAnim.autoreverses=YES;
            tAnim.timeOffset=offset;
            [self.layer addAnimation:tAnim forKey:@"shake_translation_x"];
        
            CABasicAnimation *tyAnim=[CABasicAnimation animationWithKeyPath:@"position.y"];
            tyAnim.repeatCount=HUGE_VALF;
            tyAnim.byValue=[NSNumber numberWithFloat:Y_OFFSET];
            tyAnim.duration=0.06f;
            tyAnim.autoreverses=YES;
            tyAnim.timeOffset=offset;
            [self.layer addAnimation:tyAnim forKey:@"shake_translation_y"];
        
            CABasicAnimation *rAnim=[CABasicAnimation animationWithKeyPath:@"transform.rotation"];
            rAnim.repeatCount=HUGE_VALF;
            rAnim.byValue=[NSNumber numberWithFloat:ANGLE_OFFSET];
            rAnim.duration=0.15f;
            rAnim.autoreverses=YES;
            rAnim.timeOffset=offset;
            [self.layer addAnimation:rAnim forKey:@"shake_rotation"];
        }
        -(void)stopShakeAnimation{
            [self.layer removeAnimationForKey:@"shake_translation_x"];
            [self.layer removeAnimationForKey:@"shake_translation_y"];
            [self.layer removeAnimationForKey:@"shake_rotation"];
            [UIView animateWithDuration:0.2f animations:^{
                self.transform=CGAffineTransformRotate(self.transform, ANGLE_OFFSET*0.5);
                self.transform=CGAffineTransformTranslate(self.transform, X_OFFSET*0.5, Y_OFFSET*0.5f);
            }];
        }
        
        @end
        

        希望它可以帮助某人:)

        【讨论】:

          【解决方案7】:

          在 iOS 7.0 或更高版本中,可以使用 UIKit 关键帧动画。

          [UIView animateKeyframesWithDuration:0.5 delay:0.0 options:0 animations:^{
              [UIView setAnimationCurve:UIViewAnimationCurveLinear];
          
              NSInteger repeatCount = 8;
              NSTimeInterval duration = 1.0 / (NSTimeInterval)repeatCount;
          
              for (NSInteger i = 0; i < repeatCount; i++) {
                  [UIView addKeyframeWithRelativeStartTime:i * duration relativeDuration:duration animations:^{
                      CGFloat dx = 5.0;
                      if (i == repeatCount - 1) {
                          viewToShake.transform = CGAffineTransformIdentity;
                      } else if (i % 2) {
                          viewToShake.transform = CGAffineTransformTranslate(CGAffineTransformIdentity, -dx, 0.0);
                      } else {
                          viewToShake.transform = CGAffineTransformTranslate(CGAffineTransformIdentity, +dx, 0.0);
                      }
                  }];
              }
          } completion:completion];
          

          【讨论】:

            【解决方案8】:

            【讨论】:

              【解决方案9】:

              我知道这个问题已经回答了,但是由于我之前已经实现了类似的东西,所以我觉得添加它不会有什么坏处:

              CAKeyframeAnimation *shakeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation.z"];
              NSArray *transformValues = [NSArray arrayWithObjects:
                                      [NSNumber numberWithFloat:((M_PI)/64)],
                                      [NSNumber numberWithFloat:(-((M_PI)/64))],
                                      [NSNumber numberWithFloat:((M_PI)/64)],
                                      [NSNumber numberWithFloat:(-((M_PI)/64))],
                                      [NSNumber numberWithFloat:((M_PI)/64)],
                                      [NSNumber numberWithFloat:(-((M_PI)/64))],
                                      [NSNumber numberWithFloat:0],                                
                                      nil];
              
              [shakeAnimation setValues:transformValues];
              
              NSArray *times = [NSArray arrayWithObjects:
                                [NSNumber numberWithFloat:0.14f],
                                [NSNumber numberWithFloat:0.28f],
                                [NSNumber numberWithFloat:0.42f],
                                [NSNumber numberWithFloat:0.57f],
                                [NSNumber numberWithFloat:0.71f],
                                [NSNumber numberWithFloat:0.85f],
                                [NSNumber numberWithFloat:1.0f], 
                                nil];
              
              [shakeAnimation setKeyTimes:times];
              
              shakeAnimation.fillMode = kCAFillModeForwards;
              shakeAnimation.removedOnCompletion = NO;
              shakeAnimation.duration = 0.6f;
              
              [self.viewToShake.layer addAnimation:shakeAnimation forKey:@"anim"];
              

              此外,由于您希望摇晃表示用户登录失败,您还可以考虑添加此动画,在屏幕摇晃时将屏幕染成红色:

              //Put this in the header (.h)
              @property (nonatomic, strong) UIView *redView;
              
              //Put this in the implementation (.m)
              @synthesize redView;
              
              //Put this in viewDidLoad
              self.redView = [[UIView alloc] initWithFrame:self.view.frame];
              self.redView.layer.opacity = 0.0f;
              self.redView.layer.backgroundColor = [[UIColor redColor] CGColor];
              
              //Put this wherever you check if the login failed
              CAKeyframeAnimation *redTint = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
              NSArray *transformValues = [NSArray arrayWithObjects:
                                         [NSNumber numberWithFloat:0.2f],
                                         [NSNumber numberWithFloat:0.0f],                                
                                         nil];
              
              [redTint setValues:transformValues];
              
              NSArray *times = [NSArray arrayWithObjects:
                                [NSNumber numberWithFloat:0.5f],
                                [NSNumber numberWithFloat:1.0f], 
                                nil];
              
              [redTint setKeyTimes:times];
              
              redTint.fillMode = kCAFillModeForwards;
              redTint.removedOnCompletion = NO;
              redTint.duration = 0.6f;
              
              [self.redView.layer addAnimation:shakeAnimation forKey:@"anim"];
              

              希望这会有所帮助!

              【讨论】:

                【解决方案10】:

                使用自动布局,我改编了 Chris Miles 的回答,但动画 NSLayoutConstraints 如下:

                NSLayoutConstraint *left  = ...
                NSLayoutConstraint *right = ...
                
                [UIView animateWithDuration:0.08 delay:0.0 options:UIViewAnimationOptionAutoreverse|UIViewAnimationOptionRepeat animations:^{
                    [UIView setAnimationRepeatCount:3];
                    left.constant  = 15.0;
                    right.constant = 25.0;
                    [self.view layoutIfNeeded];
                } completion:^(BOOL finished) {
                    if (finished) {
                        [UIView animateWithDuration:0.08 animations:^{
                            left.constant  = 20.0;
                            right.constant = 20.0;
                            [self.view layoutIfNeeded];
                        } completion:NULL];
                    }
                }];
                

                【讨论】:

                  【解决方案11】:

                  我用于在我的情节提要中设置的约束的解决方案。不使用 animateWithDuration。

                  @IBOutlet var balloonHorizontalConstraint: NSLayoutConstraint!
                  
                  NSTimer.scheduledTimerWithTimeInterval(0.04, target: self, selector: "animateBalloon", userInfo: nil, repeats: true)
                  
                  func animateBalloon() {
                      switch balloonHorizontalConstraint.constant {
                      case -46:
                          balloonHorizontalConstraint.constant = -50
                      default:
                          balloonHorizontalConstraint.constant = -46
                      }
                  }
                  

                  在我的情况下,动画一直在继续,但我在几秒钟后弹出我的视图控制器,这也会停止我的计时器。

                  【讨论】:

                    猜你喜欢
                    • 2011-10-24
                    • 2016-11-03
                    • 2013-08-19
                    • 1970-01-01
                    • 2015-03-17
                    • 2012-08-26
                    • 1970-01-01
                    • 2010-09-05
                    • 1970-01-01
                    相关资源
                    最近更新 更多