52

我想在我的应用程序中来回摆动图像,类似于按下 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];


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

4

13 回答 13

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

于 2009-05-30T16:54:40.460 回答
92

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
  ];
}
于 2011-04-19T20:29:28.923 回答
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];
}
于 2011-01-07T22:08:27.130 回答
9

我编写了一个示例应用程序,试图复制主屏幕摆动和图标移动:iPhone 示例代码:瓷砖

于 2011-10-18T23:22:12.930 回答
4

对于最近看到这篇文章并希望在 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)
}
于 2015-07-28T11:25:57.727 回答
2

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

要启动核心动画块,只需执行以下操作:

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

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

[UIView setAnimationBeginsFromCurrentState:YES];

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

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

[UIView setAnimationDelegate:self]

进而

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

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

更新:

您可能想查看:

[UIView setAnimationRepeatAutoreverses:YES];

这将自动来回摆动。

于 2009-05-30T16:30:48.910 回答
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"];
于 2013-02-14T14:48:24.907 回答
2

从上面的答案,我得到了 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)
}
于 2019-10-15T15:01:46.450 回答
1

晚会迟到了。我通常使用带阻尼的弹簧(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
    }

您可以通过调整initialSpringVelocity和调整效果SpringWithDamping

于 2016-05-28T13:23:16.713 回答
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)
于 2017-04-09T10:06:52.527 回答
0

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

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

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

[UIView setAnimationDidStopSelector:@selector(myMethod:finished:context:)];
于 2010-11-20T11:27:54.177 回答
0

斯威夫特 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()
}
于 2017-07-30T19:53:43.130 回答
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()
}
于 2018-10-26T06:42:19.490 回答