Animasyon tamamlandıktan sonra CABasicAnimation başlangıç ​​değerine sıfırlanıyor


143

Bir CALayer döndürüyorum ve animasyon tamamlandıktan sonra onu son konumunda durdurmaya çalışıyorum.

Ancak animasyon tamamlandıktan sonra başlangıç ​​konumuna geri döner.

(xcode dokümanlar, animasyonun mülkün değerini güncellemeyeceğini açıkça belirtir.)

bunu başarmak için herhangi bir öneri.


1
Bu, neredeyse tüm cevapların tamamen yanlış olduğu garip SO sorularından biridir - sadece tamamen YANLIŞ . Çok basit bir .presentation()şekilde "nihai, görülen" değeri elde etmeye bakıyorsunuz . Aşağıda, sunum katmanıyla yapıldığını açıklayan doğru cevapları arayın.
Fattie

Yanıtlar:


285

İşte cevap, cevabım ve Krishnan'ın bir kombinasyonu.

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

Varsayılan değer şudur kCAFillModeRemoved. (Gördüğünüz sıfırlama davranışı budur.)


6
Bu muhtemelen doğru cevap DEĞİLDİR - Krishnan'ın cevabı hakkındaki yoruma bakın. Genellikle buna VE Krishnanlara ihtiyacınız vardır; sadece biri ya da diğeri çalışmaz.
Adam

19
Bu doğru cevap değil, bakınız @Leslie Godwin'in cevabı.
Mark Ingram

6
@Leslie çözümü daha iyidir çünkü nihai değeri animasyonda değil katmanda saklar.
Matthieu Rouif

1
RemoveOnCompionion öğesini NO olarak ayarlarken dikkatli olun. Animasyon temsilcisini "kendinize" atarsanız, örneğiniz animasyon tarafından korunur. Bu nedenle, animasyonu kendiniz kaldırmayı / yok etmeyi unutursanız removeFromSuperview'i çağırdıktan sonra, örneğin dealloc çağrılmaz. Bir bellek sızıntısı olacak.
emrahgunduz

1
Bu 200+ puan cevabı tamamen, tamamen, tamamen yanlıştır.
Fattie

83

RemoveOnCompletion ile ilgili sorun, kullanıcı arabiriminin kullanıcı etkileşimine izin vermemesidir.

I tekniği, animasyondaki FROM değerini ve nesne üzerindeki TO değerini ayarlamaktır. Animasyon, başlamadan önce TO değerini otomatik olarak dolduracak ve kaldırıldığında nesneyi doğru durumda bırakacaktır.

// 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
Başlangıç ​​gecikmesi ayarlarsanız çalışmaz. eg alphaAnimation.beginTime = 1; Ayrıca fillModesöyleyebildiğim kadarıyla gerekli değil ...?
Sam

aksi halde, bu iyi bir cevaptır, kabul edilen cevap animasyon tamamlandıktan sonra bir değeri değiştirmenize izin vermez ...
Sam

2
ben de beginTimegöreceli değil dikkat etmelisiniz , örneğin kullanmalısınız:CACurrentMediaTime()+1;
Sam

'Onun' değil 'bu' - its-not-its.info. Sadece bir yazım hatasıysa özür dilerim.
fatuhoku


16

Aşağıdaki özelliği ayarlayın:

animationObject.removedOnCompletion = NO;

@Nilesh: Cevabınızı kabul edin. Bu, diğer SO kullanıcılarına cevabınız konusunda güven verecektir.
Krishnan

7
Hem .fillMode = kCAFillModeForwards hem de .removedOnCompletion = NO kullanmak zorunda kaldım. Teşekkürler!
William Rust

12

sadece kodunun içine koy

CAAnimationGroup *theGroup = [CAAnimationGroup animation];

theGroup.fillMode = kCAFillModeForwards;

theGroup.removedOnCompletion = NO;

12

Sadece anahtarını ayarlayabilir CABasicAnimationiçin positionsize katmanına eklemek zaman. Bunu yaparak, çalıştırma döngüsünde geçerli geçiş konumu üzerinde yapılan örtülü animasyonu geçersiz kılar.

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 Video Session 421 - Core Animation Essentials (videonun ortasında) hakkında daha fazla bilgi edinebilirsiniz


1
Bu, bunu yapmanın doğru yolu gibi görünüyor. Animasyon doğal olarak (varsayılan) kaldırılır ve animasyon tamamlandıktan sonra, animasyon, özellikle bu çizgiyi başlamadan önce belirlenen değerlere döndürür: someLayer.position = endPosition;. Ve WWDC'ye ref için teşekkürler!
bauerMusic

10

Bir CALayer'ın bir model katmanı ve bir sunum katmanı vardır. Bir animasyon sırasında, sunum katmanı modelden bağımsız olarak güncellenir. Animasyon tamamlandığında, sunum katmanı modeldeki değerle güncellenir. Animasyon bittikten sonra sarsıntı atlamasından kaçınmak istiyorsanız, anahtar iki katmanı senkronize tutmaktır.

Son değeri biliyorsanız, modeli doğrudan ayarlayabilirsiniz.

self.view.layer.opacity = 1;

Ancak, bitiş konumunu bilmediğiniz bir animasyonunuz varsa (örneğin, kullanıcının duraklatabileceği ve sonra tersine çevirebileceği yavaş bir solma), geçerli değeri bulmak için sunum katmanını doğrudan sorgulayabilir ve modeli güncelleyebilirsiniz.

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

Sunum katmanından değerin çekilmesi, ölçekleme veya döndürme tuş yolları için de özellikle yararlıdır. (ör transform.scale. transform.rotation)


8

Kullanmadan removedOnCompletion

Bu tekniği deneyebilirsiniz:

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
Bu en iyi işleyen cevap gibi görünüyor
rambossa

Kabul. O Ayman yorumuna @ Not gerekir başlangıç değerine .fromValue özelliğini tanımlar. Aksi takdirde, modeldeki başlangıç ​​değerinin üzerine yazmış olacaksınız ve animasyon olacak.
Jeff Collier

Bu ve @ jason-moore'un cevabı kabul edilen cevaptan daha iyidir. Kabul edilen cevapta animasyon duraklatılır, ancak yeni değer aslında özelliğe ayarlanmaz. Bu istenmeyen davranışlara neden olabilir.
laka

8

Benim sorunum pan hareketinde bir nesneyi döndürmeye çalışıyordu ve bu yüzden her hareketinde birden fazla aynı animasyon vardı. Her ikisini de aldım fillMode = kCAFillModeForwardsve isRemovedOnCompletion = falseyardımcı olmadı. Benim durumumda, her yeni animasyon eklediğimde animasyon tuşunun farklı olduğundan emin olmak zorunda kaldım :

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

Sadece ayar fillModeve removedOnCompletionbenim için çalışmadı. Aşağıdaki özelliklerin tümünü CABasicAnimation nesnesine ayarlayarak sorunu çözdüm:

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];

Bu kod myView, boyutunun% 85'ine dönüşür (3. boyut değiştirilmemiş).


7

Çekirdek animasyon iki katman hiyerarşisini korur: model katmanı ve sunum katmanı . Animasyon devam ederken, model katmanı gerçekte sağlamdır ve başlangıç ​​değerini korur. Varsayılan olarak, animasyon tamamlandığında kaldırılır. Ardından sunum katmanı, model katmanının değerine geri döner.

Basitçe ayarı removedOnCompletioniçinNO animasyon kaldırılmayacak araçlarla ve atıklar belleğe. Ek olarak, model katmanı ve sunum katmanı artık senkronize olmayacak ve bu da potansiyel hatalara yol açabilir.

Bu nedenle, özelliği doğrudan model katmanında nihai değere güncellemek daha iyi bir çözüm olacaktır.

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

Yukarıdaki kodun ilk satırından kaynaklanan herhangi bir örtülü animasyon varsa, kapatmaya çalışın:

[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];

Teşekkür ederim! Bu gerçekten kabul edilmiş cevap olmalı, çünkü bu işlevi sadece bir bant yardımcısı kullanmak yerine bu işlevselliği doğru bir şekilde açıklıyor
bplattenburg

6

Bu çalışıyor:

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")

Çekirdek animasyon, animasyon sırasında normal katmanınızın üstünde bir 'sunum katmanı' gösterir. Bu nedenle, opaklığı (veya her şeyi) animasyon bittiğinde ve sunum katmanı kaybolduğunda görünmesini istediğiniz şeye ayarlayın. Tamamlandığında bir titreşimden kaçınmak için animasyonu eklemeden önce bunu satırda yapın .

Gecikme yapmak istiyorsanız, aşağıdakileri yapın:

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")

Son olarak, 'removeOnCompletion = false' kullanırsanız, katman sonunda imha edilene kadar CAAnimations sızdırmaz - kaçının.


Harika cevap, harika ipuçları. RemoveOnCompletion yorumu için teşekkürler.
iWheelBuy

4

@Leslie Godwin'in yanıtı gerçekten iyi değil, "self.view.layer.opacity = 1;" hemen yapılır (yaklaşık bir saniye sürer), şüpheleriniz varsa lütfen alphaAnimation.duration'ı 10.0'a düzeltin. Bu satırı kaldırmanız gerekiyor.

Bu nedenle, fillMode öğesini kCAFillModeForwards olarak ve removeOnCompletion öğesini NO olarak sabitlediğinizde, animasyonun katmanda kalmasına izin verirsiniz. Animasyon temsilcisini düzeltir ve şöyle bir şey denerseniz:

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

... bu satırı yürüttüğünüz anda katman hemen geri yüklenir. Bundan kaçınmak istedik.

Animasyonu kaldırmadan önce layer özelliğini düzeltmelisiniz. Bunu dene:

- (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 bayrağı false olarak ayarlanmış ve fillMode kCAFillModeForwards olarak ayarlanmış gibi görünüyor bana da.

Bir katmana yeni animasyon uyguladıktan sonra, animasyonlu bir nesne başlangıç ​​durumuna sıfırlanır ve ardından bu durumdan canlandırılır. Ek olarak yapılması gereken, yeni animasyonu aşağıdaki gibi ayarlamadan önce model katmanının istenen özelliğini sunum katmanının özelliğine göre ayarlamaktır:

someLayer.path = ((CAShapeLayer *)[someLayer presentationLayer]).path;
[someLayer addAnimation:someAnimation forKey:@"someAnimation"];

0

En kolay çözüm örtülü animasyonlar kullanmaktır. Bu sizin için tüm bu sorunların üstesinden gelecektir:

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

Süreyi özelleştirmek istiyorsanız, aşağıdakileri kullanabilirsiniz NSAnimationContext:

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

Not: Bu yalnızca macOS'ta test edilir.

Başlangıçta bunu yaparken hiç animasyon görmedim. Sorun, bir görünüşüdür destekli tabakanın tabaka yapmasıdır olmayan kapalı animasyon. Bunu çözmek için, kendiniz bir katman eklediğinizden emin olun (görünümü katman destekli olarak ayarlamadan önce).

Bunun nasıl yapılacağıyla ilgili bir örnek:

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

Kullanmak self.wantsLayertestlerimde herhangi bir fark yaratmadı, ama bilmediğim bazı yan etkileri olabilir.


0

İşte oyun alanından bir örnek:

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. Bir animasyon modeli oluşturma
  2. Animasyonun başlangıç ​​konumunu ayarlayın (atlanabilir, geçerli katman düzeninize bağlıdır)
  3. Animasyonun bitiş konumunu ayarlama
  4. Animasyon süresini ayarlama
  5. Animasyonu bir saniyeliğine geciktir
  6. Belirlemeyin falseiçin isRemovedOnCompletion- animasyon bittikten sonra Çekirdek Animasyon temiz let
  7. İşte hilenin ilk kısmı - Animasyon başlamadan önce animasyonunuzu başlangıç ​​konumuna (2. adımda belirlediğiniz) yerleştirmek için Çekirdek Animasyon'a söylersiniz - zamanda geriye doğru uzatın
  8. Hazırlanan animasyon nesnesini kopyalayın, katmana ekleyin ve gecikmeden sonra animasyonu başlatın (5. adımda ayarladınız)
  9. İkinci bölüm katmanın doğru bitiş konumunu ayarlamaktır - animasyon silindikten sonra katmanınız doğru yerde gösterilecektir
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.