CABasicAnimation скидає початкове значення після завершення анімації


143

Я обертаю CALayer і намагаюся зупинити його в остаточному положенні після завершення анімації.

Але після завершення анімації вона повертається до початкової позиції.

(Документи xcode прямо говорять, що анімація не оновлює значення властивості.)

будь-які пропозиції, як цього досягти.


1
Це одне з тих дивних запитань ТА, де майже всі відповіді є абсолютно невірними - абсолютно ВАЖЛИВО . Ви дуже просто дивитесь, .presentation()щоб отримати "остаточне, побачене" значення. Знайдіть правильні відповіді нижче, в яких поясніть, що це зроблено з презентаційним шаром.
Fattie

Відповіді:


285

Ось відповідь, це поєднання моєї відповіді та відповіді Кришнана.

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

Значенням за замовчуванням є kCAFillModeRemoved. (Яка поведінка скидання, яку ви бачите.)


6
Це, мабуть, НЕ правильна відповідь - дивіться коментар до відповіді Кришнана. Зазвичай вам це потрібно І Крішнан; просто один чи інший не працюватимуть.
Адам

19
Це не правильна відповідь, див. Відповідь @ Леслі Годвіна нижче.
Позначити Інграма

6
@Leslie рішення краще, оскільки воно зберігає остаточне значення в шарі, а не в анімації.
Matthieu Rouif

1
Будьте уважні, коли ви встановлюєте параметр RemoveOnCompletion на NO. Якщо ви призначаєте делегату анімації "self", ваш примірник буде збережений анімацією. Отже, після виклику RemoveFromSuperview, якщо ви забудете самостійно видалити / знищити анімацію, наприклад, dealloc не буде викликано. У вас буде витік пам'яті.
emrahgunduz

1
Ця відповідь на 200 балів є абсолютно, повністю, абсолютно невірною.
Fattie

83

Проблема з 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
Не працює, якщо встановити затримку запуску. Наприклад alphaAnimation.beginTime = 1; , fillModeне потрібна, наскільки я можу сказати ...?
Сем

в іншому випадку це хороша відповідь, прийнята відповідь не дозволить вам змінити значення після того, як анімація завершиться ...
Сем

2
Я також повинен зазначити, що beginTimeце не відносно, ви повинні використовувати, наприклад:CACurrentMediaTime()+1;
Сем

Це "його" не "це" - its-not-its.info. Вибачте, якщо це була просто помилка.
fatuhoku

6
правильну відповідь, але не потрібну fillMode, дивіться докладніше у розділі " Запобігання шарам від
оснащення

16

Встановіть таке властивість:

animationObject.removedOnCompletion = NO;

@Nilesh: Прийміть свою відповідь. Це додасть впевненості іншим користувачам вашої відповіді.
Крішнан

7
Мені довелося використовувати і .fillMode = kCAFillModeForwards, і .removedOnCompletion = NO. Дякую!
Вільям Раст

12

просто покладіть його у свій код

CAAnimationGroup *theGroup = [CAAnimationGroup animation];

theGroup.fillMode = kCAFillModeForwards;

theGroup.removedOnCompletion = NO;

12

Ви можете просто встановити ключ 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

Ви можете отримати додаткову інформацію про 2011 сеанс відеозаписів Apple WWDC 421 - Основні анімаційні основи (середина відео)


1
Це виглядає як правильний спосіб зробити це. Анімація природно видаляється ( по замовчуванню), а після завершення анімації, він повертається до значень , встановлених перед тим анімація початку, а саме цього рядка: someLayer.position = endPosition;. І спасибі за відмову від WWDC!
bauerMusic

10

У CALayer є шар моделі та шар презентації. Під час анімації презентаційний шар оновлюється незалежно від моделі. Коли анімація завершена, презентаційний шар оновлюється значенням з моделі. Якщо ви хочете уникнути стрибків після закінчення анімації, головним є збереження двох шарів у синхронізації.

Якщо ви знаєте кінцеве значення, ви можете просто встановити модель безпосередньо.

self.view.layer.opacity = 1;

Але якщо у вас є анімація, де ви не знаєте кінцевої позиції (наприклад, повільне згасання, яке користувач може призупинити, а потім повернути назад), то ви можете запитувати шар презентації безпосередньо, щоб знайти поточне значення, а потім оновити модель.

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

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


8

Без використання 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
Це здається найкращою функцією відповіді
rambossa

Домовились. Зауважте в коментарі @ ayman, що для початкового значення потрібно визначити властивість .fromValue. В іншому випадку ви будете перезаписати початкове значення в моделі, і з'явиться анімація.
Джефф Коллер

Ця відповідь і відповідь @ Jason-Moore краще, ніж прийнята відповідь. У прийнятій відповіді анімація просто призупинена, але нове значення насправді не встановлено для властивості. Це може призвести до небажаної поведінки.
лака

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

Просто налаштування 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-й вимір не змінюється).


7

Основна анімація підтримує дві ієрархії шарів: шар моделі та шар презентації . Коли анімація триває, шар моделі фактично є недоторканим і зберігає його початкове значення. За замовчуванням анімація видаляється після її завершення. Потім презентаційний шар повертається до значення шару моделі.

Просто установка 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];

Дякую! Це дійсно має бути прийнятою відповіддю, оскільки це правильно пояснює цю функціональність, а не просто використання
смугової

6

Це працює:

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, поки шар врешті не буде утилізований - уникайте.


Чудова відповідь, чудові поради. Дякуємо за коментар до видаленняOnCompletion
iWheelBuy

4

@Leslie Godwin відповідь не дуже хороший, "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];
        }
    }
}

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. Друга частина полягає у встановленні правильного кінцевого положення шару - після видалення анімації ваш шар буде показаний у потрібному місці
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.