В дополнение к приведенному ниже (в котором мы захватываем текущее состояние с уровня презентации, останавливаем анимацию, восстанавливаем текущее состояние с сохраненного уровня представления и инициируем новую анимацию), существует гораздо более простое решение.
Если вы делаете блок-анимацию, если хотите остановить анимацию и запустить новую анимацию в версиях 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:
Add QuartzCore.framework to your project если вы еще не сделали. (В современных версиях Xcode часто не нужно явно делать это, поскольку оно автоматически связано с проектом.)
Импорт необходимый заголовок, если вы еще не (опять же, не требуется в современных версиях Xcode):
#import <QuartzCore/QuartzCore.h>
Есть код остановить существующую анимацию:
[self.subview.layer removeAllAnimations];
Получить ссылку на текущий уровень представления (то есть состояние представления, которое точно в данный момент):
CALayer *currentLayer = self.subview.layer.presentationLayer;
Сброс transform
(или frame
или любой другой), в соответствии с текущим значением в presentationLayer
:
self.subview.layer.transform = currentLayer.transform;
Теперь анимировать из этого 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];
}
Спасибо, Роб. Это очень хорошее и комплексное решение. Но он будет работать только на 100% именно с 'UIViewAnimationOptionCurveLinear'. Если я использую 'UIViewAnimationOptionCurveEaseInOut', обратная анимация не будет точно симметричной исходной анимации, потому что она начнет медленно и ускоряться, пока оригинальная анимация перестанет резко. Есть ли способ каким-то образом запомнить состояние анимации с точки зрения «скорости» и «ускорения», то есть получить текущую точку кривой анимации? – Mischa
@Mischa Там были бы способы сделать это, но тогда вы вышли из мира красивой простой блочной анимации (потому что нет возможности сделать часть, скажем 87,529%, кривой анимации). Это была бы адская работа (например, пользовательский код основной анимации), для чего-то, что может не наблюдаться (см. Мое альтернативное предложение ниже, которое может приблизиться к вашему запросу), но, безусловно, можно было бы сделать. Вместо этого я мог бы предположить, что если анимация будет меньше, скажем, 90%, сделайте, сделайте 'EaseOut' на обратной стороне (т. Е. Предположив, что вы быстро двигаетесь в то время), иначе' EaseInOut'. – Rob
@ Mischa Сказав это, я даже удивляюсь, что идея идеальной симметрии совершенно правильна, потому что, если вы делаете 'EaseInOut' (что означает, что вы ищете гладкую анимацию), сказать, что вы идете внезапно отменить анимацию, которая выполняется на 50% (и, таким образом, движется с максимальной скоростью) и мгновенно поворачивать ее и перемещать ее с максимальной скоростью по-другому, не так много, как «по вашему желанию», ничего себе, это было бы противоречащим оригинальному намерению «EaseInOut», не так ли? – Rob