Как создать эффект качающегося значка iPhone? - PullRequest
52 голосов
/ 30 мая 2009

Я хочу качать изображение назад и вперед в моем приложении, подобно тому, как значки 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];


Или может быть более простое решение, которое я просто пропускаю. Цените любые указатели. Спасибо!

Ответы [ 12 ]

92 голосов
/ 20 апреля 2011

Ответ Рамина был очень хорошим, но начиная с 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
  ];
}
92 голосов
/ 30 мая 2009

Простой способ сделать это:

#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. В данном случае это сам колеблющийся объект, поэтому вам не нужно полагаться на конкретные ивары, и метод можно использовать для любого общего объекта на основе UIView (т. Е. Текстовых меток, изображений и т.

14 голосов
/ 08 января 2011

Вы должны использовать 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];
}
9 голосов
/ 19 октября 2011

Я написал пример приложения, которое пытается воспроизвести колебание домашнего экрана и движение значков: Пример кода iPhone: Tiles

3 голосов
/ 28 июля 2015

Для тех, кто сталкивался с этой публикацией совсем недавно и хотел бы сделать то же самое в 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)
}
2 голосов
/ 14 февраля 2013

Вы можете создать бесконечный эффект колебания, используя 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"];
2 голосов
/ 30 мая 2009

Самый простой способ, который я знаю, это использовать Core Animation. По сути, вы создаете основной блок анимации, затем выполняете преобразование вращения, настройку и счетчик повторений. Затем 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, чтобы узнать, как реализовать этот метод, который будет вызываться при остановке анимации. По сути, внутри этого метода вы выполняете следующий шаг (то есть вращение назад или что-то еще) с новым блоком анимации, но с тем же контекстом и идентификатором анимации, а затем (при необходимости) задаете другой didStopSelector.

UPDATE:

Вы можете проверить:

[UIView setAnimationRepeatAutoreverses:YES];

это будет качаться взад и вперед автоматически.

1 голос
/ 09 апреля 2017

На основании ответа 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)
1 голос
/ 28 мая 2016

Поздно на вечеринку. Я обычно использую пружину с демпфированием (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

0 голосов
/ 26 октября 2018

Самый простой способ сделать это с помощью 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()
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...