CABasicAnimation сбрасывается до начального значения после завершения анимации

149

Я вращаю CALayer и пытаюсь остановить его в конечной позиции после завершения анимации.

Но после завершения анимации он возвращается в исходное положение.

(Документы xcode явно говорят, что анимация не обновляет значение свойства.)

любые предложения, как этого добиться.

Нилеш Укей
источник
1
Это один из тех странных ТАКИХ вопросов, на которые почти все ответы совершенно неверны - просто совершенно НЕПРАВИЛЬНЫ . Вы очень просто смотрите, .presentation()чтобы получить "окончательное, увиденное" значение. Найдите правильные ответы ниже, которые объясняют, что это сделано на уровне представления.
Fattie

Ответы:

291

Вот ответ, это комбинация моего ответа и ответа Кришнана.

cabasicanimation.fillMode = kCAFillModeForwards;
cabasicanimation.removedOnCompletion = NO;

Значение по умолчанию - kCAFillModeRemoved. (Какое поведение при сбросе вы видите.)

Нилеш Укей
источник
7
Вероятно, это НЕ правильный ответ - см. Комментарий к ответу Кришнана. Обычно вам это нужно И Кришнана; только одно или другое не сработает.
Adam
19
Это неправильный ответ, см. Ответ @Leslie Godwin ниже.
Марк Ингрэм
6
Решение @Leslie лучше, потому что оно хранит окончательное значение в слое, а не в анимации.
Матье Руиф
1
Будьте осторожны, когда для параметра removeOnCompletion установлено значение NO. Если вы назначаете делегата анимации «себе», ваш экземпляр будет сохранен анимацией. Итак, после вызова removeFromSuperview, если вы забудете удалить / уничтожить анимацию самостоятельно, например, dealloc не будет вызываться. У вас будет утечка памяти.
emrahgunduz
1
Этот ответ из 200+ пунктов полностью, полностью, совершенно неверен.
Fattie
85

Проблема с removeOnCompletion заключается в том, что элемент пользовательского интерфейса не позволяет взаимодействовать с пользователем.

Метод I заключается в установке значения FROM в анимации и значения TO на объекте. Перед запуском анимация автоматически заполнит значение TO, а после удаления оставит объект в правильном состоянии.

// fade in
CABasicAnimation *alphaAnimation = [CABasicAnimation animationWithKeyPath: @"opacity"];
alphaAnimation.fillMode = kCAFillModeForwards;

alphaAnimation.fromValue = NUM_FLOAT(0);
self.view.layer.opacity = 1;

[self.view.layer addAnimation: alphaAnimation forKey: @"fade"];
Лесли Годвин
источник
7
Не работает, если установить задержку старта. eg alphaAnimation.beginTime = 1; Кроме того, fillModeнасколько я могу судить, не требуется ...?
Сэм
в противном случае это хороший ответ, принятый ответ не позволит вам изменить значение после завершения анимации ...
Сэм
2
Я также должен отметить, что beginTimeэто не относительное понятие, вы должны использовать, например:CACurrentMediaTime()+1;
Sam
Это «это», а не «это» - its-not-its.info. Извините, если это была опечатка.
fatuhoku
6
правильный ответ, но не обязательно fillMode, см. подробнее в разделе « Предотвращение возврата слоев к
исходным
16

Установите следующее свойство:

animationObject.removedOnCompletion = NO;
Кришнан
источник
@Nilesh: Примите свой ответ. Это придаст уверенности другим пользователям SO в вашем ответе.
Кришнан
8
Мне пришлось использовать как .fillMode = kCAFillModeForwards, так и .removedOnCompletion = NO. Благодарность!
William Rust
13

Вы можете просто установить ключ CABasicAnimationна, positionкогда добавляете его на слой. Таким образом, он переопределит неявную анимацию, выполненную в позиции для текущего прохода в цикле выполнения.

CGFloat yOffset = 30;
CGPoint endPosition = CGPointMake(someLayer.position.x,someLayer.position.y + yOffset);

someLayer.position = endPosition; // Implicit animation for position

CABasicAnimation * animation =[CABasicAnimation animationWithKeyPath:@"position.y"]; 

animation.fromValue = @(someLayer.position.y);
animation.toValue = @(someLayer.position.y + yOffset);

[someLayer addAnimation:animation forKey:@"position"]; // The explicit animation 'animation' override implicit animation

Вы можете получить дополнительную информацию о Apple WWDC Video Session 421 2011 - Core Animation Essentials (середина видео)

Ягик
источник
1
Похоже, это правильный способ сделать это. Анимация естественно удаляется ( по умолчанию), а после завершения анимации, он возвращается к значениям , установленным перед тем анимация начала, а именно этой строки: someLayer.position = endPosition;. И спасибо за ссылку на WWDC!
bauerMusic
12

просто поместите это в свой код

CAAnimationGroup *theGroup = [CAAnimationGroup animation];

theGroup.fillMode = kCAFillModeForwards;

theGroup.removedOnCompletion = NO;
msk_sureshkumar
источник
11

CALayer имеет уровень модели и уровень представления. Во время анимации уровень представления обновляется независимо от модели. Когда анимация завершена, уровень представления обновляется значением из модели. Если вы хотите избежать резкого скачка после окончания анимации, главное - синхронизировать два слоя.

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

self.view.layer.opacity = 1;

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

NSNumber *opacity = [self.layer.presentationLayer valueForKeyPath:@"opacity"];
[self.layer setValue:opacity forKeyPath:@"opacity"];

Извлечение значения из уровня представления также особенно полезно для масштабирования или поворота траекторий клавиш. (например transform.scale, transform.rotation)

Джейсон Мур
источник
9

Без использования removedOnCompletion

Вы можете попробовать эту технику:

self.animateOnX(item: shapeLayer)

func animateOnX(item:CAShapeLayer)
{
    let endPostion = CGPoint(x: 200, y: 0)
    let pathAnimation = CABasicAnimation(keyPath: "position")
    //
    pathAnimation.duration = 20
    pathAnimation.fromValue = CGPoint(x: 0, y: 0)//comment this line and notice the difference
    pathAnimation.toValue =  endPostion
    pathAnimation.fillMode = kCAFillModeBoth

    item.position = endPostion//prevent the CABasicAnimation from resetting item's position when the animation finishes

    item.add(pathAnimation, forKey: nil)
}
Айман Ибрагим
источник
3
Кажется, это лучший действующий ответ
Рамбосса
Согласовано. Обратите внимание на комментарий @ayman о том, что вы должны определить свойство .fromValue для начального значения. В противном случае вы перезапишете начальное значение в модели, и будет анимация.
Джефф Коллиер
Этот ответ и ответ @jason-moore лучше, чем принятый ответ. В принятом ответе анимация просто приостанавливается, но для свойства фактически не устанавливается новое значение. Это может привести к нежелательному поведению.
laka
9

Базовая анимация поддерживает две иерархии слоев: уровень модели и уровень представления . Когда анимация выполняется, слой модели фактически не поврежден и сохраняет исходное значение. По умолчанию анимация удаляется после завершения. Затем уровень представления возвращается к значению уровня модели.

Просто установка removedOnCompletionс NOпомощью анимации не будет удалена и отходы памяти. Кроме того, уровень модели и уровень представления больше не будут синхронными, что может привести к потенциальным ошибкам.

Поэтому было бы лучше обновить свойство прямо на уровне модели до окончательного значения.

self.view.layer.opacity = 1;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
animation.fromValue = 0;
animation.toValue = 1;
[self.view.layer addAnimation:animation forKey:nil];

Если есть какая-либо неявная анимация, вызванная первой строкой приведенного выше кода, попробуйте отключить ее:

[CATransaction begin];
[CATransaction setDisableActions:YES];
self.view.layer.opacity = 1;
[CATransaction commit];

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
animation.fromValue = 0;
animation.toValue = 1;
[self.view.layer addAnimation:animation forKey:nil];
Личжэнь Ху
источник
Спасибо! Это действительно должен быть принятый ответ, поскольку он правильно объясняет эту функциональность, а не просто использует пластырь, чтобы заставить ее работать
bplattenburg
8

Итак, моя проблема заключалась в том, что я пытался повернуть объект на жесте панорамирования, и поэтому у меня было несколько одинаковых анимаций для каждого движения. У меня было и то, fillMode = kCAFillModeForwardsи другое, isRemovedOnCompletion = falseно это не помогло. В моем случае я должен был убедиться, что ключ анимации отличается каждый раз, когда я добавляю новую анимацию :

let angle = // here is my computed angle
let rotate = CABasicAnimation(keyPath: "transform.rotation.z")
rotate.toValue = angle
rotate.duration = 0.1
rotate.isRemovedOnCompletion = false
rotate.fillMode = CAMediaTimingFillMode.forwards

head.layer.add(rotate, forKey: "rotate\(angle)")
солнечный свет
источник
7

Это работает:

let animation = CABasicAnimation(keyPath: "opacity")
animation.fromValue = 0
animation.toValue = 1
animation.duration = 0.3

someLayer.opacity = 1 // important, this is the state you want visible after the animation finishes.
someLayer.addAnimation(animation, forKey: "myAnimation")

Базовая анимация показывает «слой представления» поверх обычного слоя во время анимации. Поэтому установите непрозрачность (или что-то еще) на то, что вы хотите видеть, когда анимация закончится и слой презентации исчезнет. Сделайте это в строке перед добавлением анимации, чтобы избежать мерцания после ее завершения.

Если вы хотите отсрочку, сделайте следующее:

let animation = CABasicAnimation(keyPath: "opacity")
animation.fromValue = 0
animation.toValue = 1
animation.duration = 0.3
animation.beginTime = someLayer.convertTime(CACurrentMediaTime(), fromLayer: nil) + 1
animation.fillMode = kCAFillModeBackwards // So the opacity is 0 while the animation waits to start.

someLayer.opacity = 1 // <- important, this is the state you want visible after the animation finishes.
someLayer.addAnimation(animation, forKey: "myAnimation")

Наконец, если вы используете «removeOnCompletion = false», произойдет утечка CAAnimations, пока слой в конечном итоге не будет удален - избегайте.

Крис
источник
Отличный ответ, отличные советы. Спасибо за комментарий removeOnCompletion.
iWheelBuy
7

Просто установка fillModeи removedOnCompletionне работала у меня. Я решил проблему, установив все свойства ниже для объекта CABasicAnimation:

CABasicAnimation* ba = [CABasicAnimation animationWithKeyPath:@"transform"];
ba.duration = 0.38f;
ba.fillMode = kCAFillModeForwards;
ba.removedOnCompletion = NO;
ba.autoreverses = NO;
ba.repeatCount = 0;
ba.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.85f, 0.85f, 1.0f)];
[myView.layer addAnimation:ba forKey:nil];

Этот код трансформируется myViewдо 85% своего размера (3-е измерение без изменений).

Паула Васконселос Гейрос
источник
4

Ответ @Leslie Godwin не очень хорош: "self.view.layer.opacity = 1;" выполняется немедленно (это занимает около секунды), исправьте alphaAnimation.duration до 10.0, если у вас есть сомнения. Вы должны удалить эту строку.

Итак, когда вы исправляете fillMode на kCAFillModeForwards и removeOnCompletion на NO, вы позволяете анимации оставаться в слое. Если вы исправите делегат анимации и попробуете что-то вроде:

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
 [theLayer removeAllAnimations];
}

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

Перед удалением анимации необходимо исправить свойство слоя. Попробуй это:

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
     if([anim isKindOfClass:[CABasicAnimation class] ]) // check, because of the cast
    {
        CALayer *theLayer = 0;
        if(anim==[_b1 animationForKey:@"opacity"])
            theLayer = _b1; // I have two layers
        else
        if(anim==[_b2 animationForKey:@"opacity"])
            theLayer = _b2;

        if(theLayer)
        {
            CGFloat toValue = [((CABasicAnimation*)anim).toValue floatValue];
            [theLayer setOpacity:toValue];

            [theLayer removeAllAnimations];
        }
    }
}
tontonCD
источник
1

Кажется, что для флага removeOnCompletion установлено значение false, а для fillMode установлено значение kCAFillModeForwards. У меня тоже не работает.

После того, как я применил новую анимацию к слою, анимированный объект сбрасывается в исходное состояние, а затем анимируется из этого состояния. Что необходимо сделать дополнительно, так это установить желаемое свойство слоя модели в соответствии со свойством уровня представления перед установкой новой анимации следующим образом:

someLayer.path = ((CAShapeLayer *)[someLayer presentationLayer]).path;
[someLayer addAnimation:someAnimation forKey:@"someAnimation"];
Бартош Ольшановски
источник
0

Самое простое решение - использовать неявную анимацию. Это решит все эти проблемы за вас:

self.layer?.backgroundColor = NSColor.red.cgColor;

Если вы хотите настроить, например, продолжительность, вы можете использовать NSAnimationContext:

    NSAnimationContext.beginGrouping();
    NSAnimationContext.current.duration = 0.5;
    self.layer?.backgroundColor = NSColor.red.cgColor;
    NSAnimationContext.endGrouping();

Примечание: это проверено только на macOS.

Я изначально при этом не видел никакой анимации. Проблема в том, что слой слоя с поддержкой просмотра не имеет неявной анимации. Чтобы решить эту проблему, убедитесь, что вы добавили слой самостоятельно (перед тем, как установить вид с поддержкой слоев).

Пример того, как это сделать:

override func awakeFromNib() {
    self.layer = CALayer();
    //self.wantsLayer = true;
}

Использование self.wantsLayerне имело никакого значения в моем тестировании, но могло иметь некоторые побочные эффекты, о которых я не знаю.

паксос
источник
0

Вот образец с детской площадки:

import PlaygroundSupport
import UIKit

let resultRotation = CGFloat.pi / 2
let view = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 200.0, height: 300.0))
view.backgroundColor = .red

//--------------------------------------------------------------------------------

let rotate = CABasicAnimation(keyPath: "transform.rotation.z") // 1
rotate.fromValue = CGFloat.pi / 3 // 2
rotate.toValue = resultRotation // 3
rotate.duration = 5.0 // 4
rotate.beginTime = CACurrentMediaTime() + 1.0 // 5
// rotate.isRemovedOnCompletion = false // 6
rotate.fillMode = .backwards // 7

view.layer.add(rotate, forKey: nil) // 8
view.layer.setAffineTransform(CGAffineTransform(rotationAngle: resultRotation)) // 9

//--------------------------------------------------------------------------------

PlaygroundPage.current.liveView = view
  1. Создать анимационную модель
  2. Установите начальную позицию анимации (можно пропустить, это зависит от вашего текущего макета слоя)
  3. Установить конечную позицию анимации
  4. Установить продолжительность анимации
  5. Задержка анимации на секунду
  6. Не установлено , falseчтобы isRemovedOnCompletion- пусть Core Animation чистой после того , как анимация закончена
  7. Вот первая часть трюка - вы говорите Core Animation поместить вашу анимацию в начальную позицию (вы устанавливаете на шаге № 2) еще до того, как анимация будет запущена - растяните ее назад во времени.
  8. Скопируйте подготовленный объект анимации, добавьте его на слой и запустите анимацию после задержки (вы установили на шаге 5)
  9. Вторая часть - установить правильное конечное положение слоя - после удаления анимации ваш слой будет показан в правильном месте.
Аднако
источник