2013-04-30 1 views
13

У меня есть анимация UIView, так что она сжимается, когда пользователь прикасается к кнопке переключения, и она снова расширяется до первоначального размера, когда пользователь снова прикасается к кнопке. Пока все работает отлично. Проблема в том, что анимация занимает некоторое время - например. 3 секунды. В течение этого времени я все еще хочу, чтобы пользователь мог взаимодействовать с интерфейсом. Поэтому, когда пользователь снова прикасается к кнопке во время анимации, анимация должна останавливаться прямо там, где она есть, и наоборот.Как остановить и изменить анимацию UIView?

В компании Apple Q & Как я нашел способ, чтобы приостановить все анимации сразу:

https://developer.apple.com/library/ios/#qa/qa2009/qa1673.html

Но я не вижу способ обратить вспять анимацию здесь (и опустить остальную часть начальная анимация). Как это сделать?

- (IBAction)toggleMeter:(id)sender { 
    if (self.myView.hidden) {   
     self.myView.hidden = NO; 
     [UIView animateWithDuration:3 animations:^{ 
      self.myView.transform = expandMatrix; 
     } completion:nil]; 
    } else { 
     [UIView animateWithDuration:3 animations:^{ 
      self.myView.transform = shrinkMatrix; 
     } completion:^(BOOL finished) { 
      self.myView.hidden = YES; 
     }]; 
    } 
} 

ответ

41

В дополнение к приведенному ниже (в котором мы захватываем текущее состояние с уровня презентации, останавливаем анимацию, восстанавливаем текущее состояние с сохраненного уровня представления и инициируем новую анимацию), существует гораздо более простое решение.

Если вы делаете блок-анимацию, если хотите остановить анимацию и запустить новую анимацию в версиях iOS до 8.0, вы можете просто использовать опцию UIViewAnimationOptionBeginFromCurrentState. (Эффективно в iOS 8, поведение по умолчанию заключается не только в том, чтобы начать с текущего состояния, но и сделать это таким образом, который отражает как текущее местоположение, так и текущую скорость, что делает его в значительной степени ненужным беспокоиться об этой проблеме вообще См. WWDC 2014 видео Building Interruptible and Responsive Interactions для получения дополнительной информации.)

[UIView animateWithDuration:3.0 
         delay:0.0 
        options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction 
       animations:^{ 
        // specify the new `frame`, `transform`, etc. here 
       } 
       completion:NULL]; 

Вы можете достичь этого путем остановки текущей анимации и начать новую анимацию, где ток один кончил. Вы можете сделать это с Quartz 2D:

  1. Add QuartzCore.framework to your project если вы еще не сделали. (В современных версиях Xcode часто не нужно явно делать это, поскольку оно автоматически связано с проектом.)

  2. Импорт необходимый заголовок, если вы еще не (опять же, не требуется в современных версиях Xcode):

    #import <QuartzCore/QuartzCore.h> 
    
  3. Есть код остановить существующую анимацию:

    [self.subview.layer removeAllAnimations]; 
    
  4. Получить ссылку на текущий уровень представления (то есть состояние представления, которое точно в данный момент):

    CALayer *currentLayer = self.subview.layer.presentationLayer; 
    
  5. Сброс transform (или frame или любой другой), в соответствии с текущим значением в presentationLayer:

    self.subview.layer.transform = currentLayer.transform; 
    
  6. Теперь анимировать из этого transform (или frame или любой другой) на новое значение:

    [UIView animateWithDuration:1.0 
             delay:0.0 
            options:UIViewAnimationOptionAllowUserInteraction 
           animations:^{ 
            self.subview.layer.transform = newTransform; 
           } 
           completion:NULL]; 
    

Сложив это все вместе, вот процедура, которая переключает масштаб преобразования от 2.0x до ide ntify и обратно:

- (IBAction)didTouchUpInsideAnimateButton:(id)sender 
{ 
    CALayer *currentLayer = self.subview.layer.presentationLayer; 

    [self.subview.layer removeAllAnimations]; 

    self.subview.layer.transform = currentLayer.transform; 

    CATransform3D newTransform; 

    self.large = !self.large; 

    if (self.large) 
     newTransform = CATransform3DMakeScale(2.0, 2.0, 1.0); 
    else 
     newTransform = CATransform3DIdentity; 

    [UIView animateWithDuration:1.0 
          delay:0.0 
         options:UIViewAnimationOptionAllowUserInteraction 
        animations:^{ 
         self.subview.layer.transform = newTransform; 
        } 
        completion:NULL]; 
} 

Или, если вы хотите, чтобы переключить frame размеры от 100x100 до 200x200 и обратно:

- (IBAction)didTouchUpInsideAnimateButton:(id)sender 
{ 
    CALayer *currentLayer = self.subview.layer.presentationLayer; 

    [self.subview.layer removeAllAnimations]; 

    CGRect newFrame = currentLayer.frame; 

    self.subview.frame = currentLayer.frame; 

    self.large = !self.large; 

    if (self.large) 
     newFrame.size = CGSizeMake(200.0, 200.0); 
    else 
     newFrame.size = CGSizeMake(100.0, 100.0); 

    [UIView animateWithDuration:1.0 
          delay:0.0 
         options:UIViewAnimationOptionAllowUserInteraction 
        animations:^{ 
         self.subview.frame = newFrame; 
        } 
        completion:NULL]; 
} 

Кстати, в то время как он вообще не имеет никакого значения для действительно быстрые анимации, для медленной анимации, такой как ваша, вы можете установить длительность анимации обратного просмотра так же, как и то, как далеко продвинулись в вашей текущей анимации (например, если вы используете 0,5 секунды в анимации на 3,0 секунды, когда вы меняете, вы, вероятно, не хотите принимать 3 .0 секунд, чтобы отменить эту небольшую часть анимации, которую вы сделали до сих пор, а всего лишь 0,5 секунды). Таким образом, это может выглядеть следующим образом:

- (IBAction)didTouchUpInsideAnimateButton:(id)sender 
{ 
    CFTimeInterval duration = kAnimationDuration;    // default the duration to some constant 
    CFTimeInterval currentMediaTime = CACurrentMediaTime(); // get the current media time 
    static CFTimeInterval lastAnimationStart = 0.0;   // media time of last animation (zero the first time) 

    // if we previously animated, then calculate how far along in the previous animation we were 
    // and we'll use that for the duration of the reversing animation; if larger than 
    // kAnimationDuration that means the prior animation was done, so we'll just use 
    // kAnimationDuration for the length of this animation 

    if (lastAnimationStart) 
     duration = MIN(kAnimationDuration, (currentMediaTime - lastAnimationStart)); 

    // save our media time for future reference (i.e. future invocations of this routine) 

    lastAnimationStart = currentMediaTime; 

    // if you want the animations to stay relative the same speed if reversing an ongoing 
    // reversal, you can backdate the lastAnimationStart to what the lastAnimationStart 
    // would have been if it was a full animation; if you don't do this, if you repeatedly 
    // reverse a reversal that is still in progress, they'll incrementally speed up. 

    if (duration < kAnimationDuration) 
     lastAnimationStart -= (kAnimationDuration - duration); 

    // grab the state of the layer as it is right now 

    CALayer *currentLayer = self.subview.layer.presentationLayer; 

    // cancel any animations in progress 

    [self.subview.layer removeAllAnimations]; 

    // set the transform to be as it is now, possibly in the middle of an animation 

    self.subview.layer.transform = currentLayer.transform; 

    // toggle our flag as to whether we're looking at large view or not 

    self.large = !self.large; 

    // set the transform based upon the state of the `large` boolean 

    CATransform3D newTransform; 

    if (self.large) 
     newTransform = CATransform3DMakeScale(2.0, 2.0, 1.0); 
    else 
     newTransform = CATransform3DIdentity; 

    // now animate to our new setting 

    [UIView animateWithDuration:duration 
          delay:0.0 
         options:UIViewAnimationOptionAllowUserInteraction 
        animations:^{ 
         self.subview.layer.transform = newTransform; 
        } 
        completion:NULL]; 
} 
+0

Спасибо, Роб. Это очень хорошее и комплексное решение. Но он будет работать только на 100% именно с 'UIViewAnimationOptionCurveLinear'. Если я использую 'UIViewAnimationOptionCurveEaseInOut', обратная анимация не будет точно симметричной исходной анимации, потому что она начнет медленно и ускоряться, пока оригинальная анимация перестанет резко. Есть ли способ каким-то образом запомнить состояние анимации с точки зрения «скорости» и «ускорения», то есть получить текущую точку кривой анимации? – Mischa

+0

@Mischa Там были бы способы сделать это, но тогда вы вышли из мира красивой простой блочной анимации (потому что нет возможности сделать часть, скажем 87,529%, кривой анимации). Это была бы адская работа (например, пользовательский код основной анимации), для чего-то, что может не наблюдаться (см. Мое альтернативное предложение ниже, которое может приблизиться к вашему запросу), но, безусловно, можно было бы сделать. Вместо этого я мог бы предположить, что если анимация будет меньше, скажем, 90%, сделайте, сделайте 'EaseOut' на обратной стороне (т. Е. Предположив, что вы быстро двигаетесь в то время), иначе' EaseInOut'. – Rob

+0

@ Mischa Сказав это, я даже удивляюсь, что идея идеальной симметрии совершенно правильна, потому что, если вы делаете 'EaseInOut' (что означает, что вы ищете гладкую анимацию), сказать, что вы идете внезапно отменить анимацию, которая выполняется на 50% (и, таким образом, движется с максимальной скоростью) и мгновенно поворачивать ее и перемещать ее с максимальной скоростью по-другому, не так много, как «по вашему желанию», ничего себе, это было бы противоречащим оригинальному намерению «EaseInOut», не так ли? – Rob

1

Существует общая уловка, которую Вы можете использовать, чтобы сделать это, но надо писать отдельный метод сжиматься (и другой подобный один расширить):

- (void) shrink { 
    [UIView animateWithDuration:0.3 
        animations:^{ 
         self.myView.transform = shrinkALittleBitMatrix; 
        } 
        completion:^(BOOL finished){ 
          if (continueShrinking && size>0) { 
           size=size-1; 
           [self shrink];  
          } 
        }]; 
} 

Так вот, трюк состоит в том, чтобы сломать 3-секундную анимацию сжатия на 10 анимаций (или более 10, конечно) по 0,3 сек каждая, в которой вы сокращаете 1/10 всей анимации: shrinkALittleBitMatrix. После завершения каждой анимации вы вызываете тот же самый метод только тогда, когда значение bool ivar continueShrinking истинно, и когда int ivar size положителен (размер в полном размере будет size = 10, а вид с минимальным размером будет size = 0). Когда вы нажимаете кнопку, вы меняете ivar continueShrinking на FALSE, а затем вызываете expand. Это остановит анимацию менее чем за 0,3 секунды.

Ну, вы должны заполнить детали, но я надеюсь, что это поможет.

+0

Это хорошее решение для функции, которая должна быть частью структуры. Но проблема здесь в основном такая же, как с решением Роба: она поддерживает только линейные анимации, потому что каждая частичная анимация имеет одинаковую продолжительность. Конечно, я мог бы подражать поведению «UIViewAnimationOptionCurveEaseInOut», изменяя продолжительность в зависимости от значения размера «ivar» и, таким образом, имитировать ускорение и замедление. Но это было бы еще одним обходным решением, делающим вещи более сложными и менее аккуратными. – Mischa

0

Первый: как удалить или отменить анимацию с точки зрения?

[view.layer removeAllAnimations] 

, если представление имеет много анимации, например, одна анимации двигаться сверху вниз, другой ход слева направо;

Вы можете отменить или удалить специальную анимацию, как это:

[view.layer removeAnimationForKey:@"someKey"]; 
// the key is you assign when you create a animation 
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"someKey"]; 

, когда вы сделаете это, анимация остановится, он будет ссылаться на это делегат:

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag 

если флаг == 1, показать анимацию завершена. если флаг == 0, укажите, что анимация не завершена, она может быть отменена, удалена.

Во-вторых: поэтому вы можете делать то, что хотите сделать в этом делетете.

если вы хотите получить кадр вида по когда удалить код преобразования приложений, вы можете сделать это:

currentFrame = view.layer.presentationlayer.frame; 

Примечание:

когда вы получите текущий кадр и удалить анимацию, то view также будет анимировать период времени, поэтому currentFrame не является последним кадром на экране устройства.

Я не могу решить этот вопрос сейчас. если когда-нибудь я смогу, я обновлю этот вопрос.