如何创建iPhone的摇摆图标效果?

时间:2009-05-30 08:43:15

标签: iphone ios animation rotation

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

这是我第一次尝试不使用动画GIF的动画。我认为这个想法是来回轻微旋转图像以产生摆动效果。我已经看过使用CABasicAnimation和CAKeyframeAnimation。 CABasicAnimation每次重复时都会创建一个抖动,因为它会跳转到from位置并且不会向内插回。 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];


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

13 个答案:

答案 0 :(得分:92)

简单的方法:

#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上下文值将任何内容传递给start / stop方法。在这种情况下,它是摆动对象本身,因此您不必依赖特定的ivars,并且该方法可用于任何基于UIView的通用对象(即文本标签,图像等)

答案 1 :(得分:92)

Ramin的答案非常好,但是从OS4开始,在一个简单的函数中使用animateWithDuration也可以达到同样的效果。

(改编他的未来googlers的例子)

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

答案 2 :(得分:14)

您应该使用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];
}

答案 3 :(得分:9)

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

答案 4 :(得分:3)

对于最近遇到过此帖子的人,并希望在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)
}

答案 5 :(得分:2)

我知道最简单的方法是使用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];

这将自动来回摆动。

答案 6 :(得分:2)

您可以使用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"];

答案 7 :(得分:1)

晚会。我通常使用带阻尼的弹簧(iOS7和更新版本)。在swift中它看起来像:

    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

来调整效果

答案 8 :(得分:1)

根据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)

答案 9 :(得分:0)

Ramin给出的代码运行良好。但是如果您使用tabbar应用程序并移动到下一个标签项然后再次返回上一个标签项,您将看到您的视图已移至左侧,每个所以最好的做法是使用ViewWillAppear方法作为。

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

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

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

答案 10 :(得分:0)

Swift 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()
}

答案 11 :(得分:0)

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

答案 12 :(得分:0)

从以上答案中,我得到了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)
}