CABasicAnimation сбрасывается к начальному значению после завершения анимации
Я вращаю CALayer и пытаюсь остановить его в его окончательном положении после завершения анимации.
Но после завершения анимации он сбрасывается в исходное положение.
(Документы xcode явно говорят, что анимация не будет обновлять значение свойства.)
любые предложения, как этого добиться.
14 ответов
Изменить: вот ответ, это сочетание моего ответа и Кришнана.
cabasicanimation.fillMode = kCAFillModeForwards;
cabasicanimation.removedOnCompletion = NO;
Значением по умолчанию является kCAFillModeRemoved
, (Какое поведение сброса вы видите.)
Проблема с removeOnCompletion заключается в том, что элемент пользовательского интерфейса не позволяет взаимодействовать с пользователем.
Я использую технику для установки значения 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"];
Базовая анимация поддерживает две иерархии слоев: уровень модели и уровень представления. Когда анимация выполняется, слой модели фактически не поврежден и сохраняет свое первоначальное значение. По умолчанию анимация удаляется после ее завершения. Затем уровень представления возвращается к значению уровня модели.
Просто настройка 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;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
animation.fromValue = 0;
animation.toValue = 1;
[self.view.layer addAnimation:animation forKey:nil];
[CATransaction commit];
- Ссылка:
- Анимации, объясненные objc.io.
- "Программирование на iOS 7, раздвигающее границы", Роб Нейпир и Мугунт Кумар.
Установите следующее свойство:
animationObject.removedOnCompletion = NO;
Вы можете просто установить ключ 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 - Основные принципы анимации (середина видео).
Это работает:
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 до тех пор, пока слой не будет в конечном итоге удален - избегайте
CALayer имеет уровень модели и уровень представления. Во время анимации уровень представления обновляется независимо от модели. Когда анимация завершена, уровень представления обновляется в соответствии со значением из модели. Если вы хотите избежать резкого скачка после окончания анимации, ключ состоит в том, чтобы синхронизировать два слоя.
Если вы знаете конечное значение, вы можете просто установить модель напрямую.
self.view.layer.opacity = 1;
Но если у вас есть анимация, в которой вы не знаете конечную позицию (например, медленное затухание, которое пользователь может приостановить, а затем развернуть), то вы можете напрямую запросить уровень представления, чтобы найти текущее значение, а затем обновить модель.
NSNumber *opacity = [self.layer.presentationLayer valueForKeyPath:@"opacity"];
[self.layer setValue:opacity forKeyPath:@"opacity"];
Извлечение значения из уровня представления также особенно полезно для масштабирования или вращения путей. (например transform.scale
, transform.rotation
)
Просто поместите это в свой код
CAAnimationGroup *theGroup = [CAAnimationGroup animation];
theGroup.fillMode = kCAFillModeForwards;
theGroup.removedOnCompletion = NO;
Поэтому моя проблема заключалась в том, что я пытался вращать объект при жесте панорамирования, поэтому у меня было несколько одинаковых анимаций при каждом движении. У меня были оба 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 = kCAFillModeForwards
head.layer.add(rotate, forKey: "rotate\(angle)")
Без использования 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)
}
Просто настройка 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-е измерение без изменений).
@ Лесли Годвин не очень хороший ответ: "self.view.layer.opacity = 1;" выполняется немедленно (это занимает около одной секунды), пожалуйста, исправьте alphaAnimation.duration на 10.0, если у вас есть сомнения. Вы должны удалить эту строку.
Таким образом, когда вы фиксируете fillMode в kCAFillModeForwards и удаляете OnCompletion в 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];
}
}
}
Самое простое решение - использовать неявную анимацию. Это решит все эти проблемы для вас:
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
не имел никакого значения в моем тестировании, но у него могли быть некоторые побочные эффекты, о которых я не знаю.
Вот образец с детской площадки:
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
- Создать анимационную модель
- Установите начальную позицию анимации (можно пропустить, это зависит от вашего текущего макета слоя)
- Установить конечную позицию анимации
- Установить продолжительность анимации
- Задержка анимации на секунду
- Не устанавливать
false
кisRemovedOnCompletion
- позвольте Core Animation очистить после завершения анимации - Вот первая часть трюка - вы говорите Core Animation поместить вашу анимацию в начальную позицию (вы устанавливаете на шаге № 2) еще до того, как анимация будет запущена - растяните ее назад во времени.
- Скопируйте подготовленный объект анимации, добавьте его на слой и запустите анимацию после задержки (вы установили на шаге 5)
- Вторая часть - установить правильное конечное положение слоя - после удаления анимации ваш слой будет показан в правильном месте.
Кажется, что установлен флаг falseOnCompletion, установленный в false, и fillMode, установленный в kCAFillModeForwards, также не работает для меня.
После того, как я наложил новую анимацию на слой, анимируемый объект сбрасывается в исходное состояние и затем анимируется из этого состояния. Что необходимо сделать дополнительно, это установить желаемое свойство слоя модели в соответствии со свойством его уровня представления перед установкой новой анимации, например, так:
someLayer.path = ((CAShapeLayer *)[someLayer presentationLayer]).path;
[someLayer addAnimation:someAnimation forKey:@"someAnimation"];