CABasicAnimation ulang ke nilai awal setelah animasi selesai

143

Saya memutar CALayer dan mencoba menghentikannya pada posisi terakhir setelah animasi selesai.

Tetapi setelah animasi selesai, pengaturan ulang ke posisi awal.

(xcode docs secara eksplisit mengatakan bahwa animasi tidak akan memperbarui nilai properti.)

ada saran bagaimana mencapai ini.

Nilesh Ukey
sumber
1
Ini adalah salah satu pertanyaan SO aneh di mana hampir semua jawaban benar-benar salah - hanya SALAH . Anda cukup melihat .presentation()untuk mendapatkan nilai "akhir, terlihat". Cari ke bawah untuk jawaban yang benar di bawah ini yang menjelaskan hal itu dilakukan dengan lapisan presentasi.
Fattie

Jawaban:

285

Inilah jawabannya, kombinasi dari jawaban saya dan jawaban Krishnan.

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

Nilai standarnya adalah kCAFillModeRemoved. (Yang merupakan perilaku reset yang Anda lihat.)

Nilesh Ukey
sumber
6
Ini mungkin BUKAN jawaban yang benar - lihat komentar pada jawaban Krishnan. Anda biasanya membutuhkan ini DAN milik Krishnan; satu saja tidak akan bekerja.
Adam
19
Ini bukan jawaban yang benar, lihat jawaban @Leslie Godwin di bawah ini.
Mark Ingram
6
@ Leslie solusi lebih baik karena menyimpan nilai akhir di layer dan bukan di animasi.
Matthieu Rouif
1
Hati-hati saat Anda mengatur dihapusOnCompletion ke NO. Jika Anda menetapkan delegasi animasi ke "diri", instance Anda akan disimpan oleh animasi. Jadi, setelah memanggil removeFromSuperview jika Anda lupa untuk menghapus / menghancurkan animasi sendiri, dealloc misalnya tidak akan dipanggil. Anda akan mengalami kebocoran memori.
emrahgunduz
1
Jawaban 200 poin ini benar-benar, sepenuhnya, benar-benar salah.
Fattie
83

Masalah dengan penghapusan OnCompletion adalah elemen UI tidak memungkinkan interaksi pengguna.

Teknik I adalah mengatur nilai FROM dalam animasi dan nilai TO pada objek. Animasi akan secara otomatis mengisi nilai TO sebelum dimulai, dan ketika dihapus akan meninggalkan objek pada keadaan yang benar.

// 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"];
Leslie Godwin
sumber
7
Tidak berfungsi jika Anda menetapkan penundaan mulai. misalnya alphaAnimation.beginTime = 1; Juga, fillModetidak diperlukan sejauh yang saya tahu ...?
Sam
jika tidak, ini adalah jawaban yang baik, jawaban yang diterima tidak akan membiarkan Anda mengubah nilai setelah animasi selesai ...
Sam
2
saya juga harus mencatat bahwa beginTimeini tidak relatif, Anda harus menggunakan misalnya:CACurrentMediaTime()+1;
Sam
Ini 'bukan' itu "- its-not-its.info. Maaf kalau itu hanya salah ketik.
fatuhoku
6
jawaban yang benar, tetapi tidak perlu fillMode, lihat lebih banyak di Mencegah Lapisan Dari Memotret Kembali ke Nilai Asli Saat Menggunakan CAfanimasi Eksplisit
likid1412
16

Setel properti berikut:

animationObject.removedOnCompletion = NO;
Krishnan
sumber
@Nilesh: Terima jawaban Anda. Itu akan memberikan kepercayaan diri pengguna SO lainnya pada jawaban Anda.
Krishnan
7
Saya harus menggunakan keduanya .fillMode = kCAFillModeForwards dan .removedOnCompletion = TIDAK. Terima kasih!
William Rust
12

masukkan saja ke dalam kode Anda

CAAnimationGroup *theGroup = [CAAnimationGroup animation];

theGroup.fillMode = kCAFillModeForwards;

theGroup.removedOnCompletion = NO;
msk_sureshkumar
sumber
12

Anda bisa mengatur kunci CABasicAnimationuntuk positionketika Anda menambahkannya ke layer. Dengan melakukan ini, itu akan menimpa animasi implisit yang dilakukan pada posisi untuk operan saat ini di run loop.

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

Anda dapat memiliki informasi lebih lanjut tentang Sesi Video Apple WWDC 2011 421 - Core Animation Essentials (tengah video)

Yageek
sumber
1
Ini sepertinya cara yang tepat untuk melakukan ini. Animasi secara alami dihapus (default), dan setelah animasi selesai, ia kembali ke nilai-nilai yang ditetapkan sebelum animasi dimulai, khususnya baris ini: someLayer.position = endPosition;. Dan terima kasih atas referensi ke WWDC!
bauerMusic
10

CALayer memiliki lapisan model dan lapisan presentasi. Selama animasi, lapisan presentasi diperbarui secara terpisah dari model. Ketika animasi selesai, lapisan presentasi diperbarui dengan nilai dari model. Jika Anda ingin menghindari lompatan yang menggelegar setelah animasi berakhir, kuncinya adalah menjaga kedua lapisan tetap sinkron.

Jika Anda tahu nilai akhirnya, Anda bisa langsung mengatur modelnya.

self.view.layer.opacity = 1;

Tetapi jika Anda memiliki animasi di mana Anda tidak tahu posisi akhir (misalnya memudar lambat bahwa pengguna dapat menghentikan sementara dan kemudian membalikkan), maka Anda dapat meminta lapisan presentasi langsung untuk menemukan nilai saat ini, dan kemudian memperbarui model.

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

Menarik nilai dari lapisan presentasi juga sangat berguna untuk penskalaan atau keypath rotasi. (misalnya transform.scale, transform.rotation)

Jason Moore
sumber
8

Tanpa menggunakan removedOnCompletion

Anda dapat mencoba teknik ini:

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)
}
Ayman Ibrahim
sumber
3
Ini tampaknya menjadi jawaban yang berfungsi terbaik
rambossa
Sepakat. Catatan @ ayman komentar bahwa Anda harus mendefinisikan properti .fromValue ke nilai awal. Jika tidak, Anda akan menimpa nilai awal dalam model, dan akan ada animasi.
Jeff Collier
Jawaban yang satu ini dan @ jason-moore lebih baik daripada jawaban yang diterima. Dalam jawaban yang diterima animasi hanya dijeda tetapi nilai baru tidak benar-benar disetel ke properti. Ini dapat mengakibatkan perilaku yang tidak diinginkan.
laka
8

Jadi masalah saya adalah saya mencoba memutar objek pada gerakan pan dan jadi saya memiliki beberapa animasi yang identik pada setiap gerakan. Saya memiliki keduanya fillMode = kCAFillModeForwardsdan isRemovedOnCompletion = falsetetapi itu tidak membantu. Dalam kasus saya, saya harus memastikan bahwa kunci animasi berbeda setiap kali saya menambahkan animasi baru :

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

Pengaturan sederhana fillModedan removedOnCompletiontidak berhasil untuk saya. Saya memecahkan masalah dengan mengatur semua properti di bawah ini ke objek 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];

Kode ini berubah myViewmenjadi 85% dari ukurannya (dimensi 3 tidak berubah).

Paula Vasconcelos Gueiros
sumber
7

Animasi inti mempertahankan dua hierarki lapisan: lapisan model dan lapisan presentasi . Ketika animasi sedang berlangsung, lapisan model sebenarnya utuh dan menyimpannya nilai awal. Secara default, animasi dihapus setelah selesai. Kemudian lapisan presentasi jatuh kembali ke nilai lapisan model.

Cukup mengatur removedOnCompletionkeNO sarana animasi tidak akan dihapus dan memori limbah. Selain itu, lapisan model dan lapisan presentasi tidak akan lagi sinkron, yang dapat menyebabkan potensi bug.

Jadi itu akan menjadi solusi yang lebih baik untuk memperbarui properti secara langsung pada lapisan model ke nilai akhir.

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

Jika ada animasi tersirat yang disebabkan oleh baris pertama kode di atas, coba matikan jika:

[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];
Lizhen Hu
sumber
Terima kasih! Ini benar-benar harus menjadi jawaban yang diterima, karena ini menjelaskan fungsi ini dengan benar alih-alih hanya menggunakan bantuan band untuk membuatnya berfungsi
bplattenburg
6

Ini bekerja:

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

Animasi inti menunjukkan 'lapisan presentasi' di atas lapisan normal Anda selama animasi. Jadi atur opacity (atau apa pun) ke apa yang ingin Anda lihat ketika animasi selesai dan lapisan presentasi hilang. Lakukan ini di telepon sebelum Anda menambahkan animasi untuk menghindari kedipan ketika selesai.

Jika Anda ingin menunda, lakukan hal berikut:

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

Akhirnya, jika Anda menggunakan 'RemoveOnCompletion = false' itu akan membocorkan CAAnimations sampai lapisan akhirnya dibuang - hindari.

Chris
sumber
Jawaban yang bagus, tips yang bagus. Terima kasih telah menghapus komentar OnOnCompletion.
iWheelBeli
4

@Leslie Godwin tidak benar-benar baik, "self.view.layer.opacity = 1;" dilakukan segera (dibutuhkan sekitar satu detik), harap perbaiki alphaAnimation.duration hingga 10.0, jika Anda ragu. Anda harus menghapus baris ini.

Jadi, ketika Anda memperbaiki fillMode ke kCAFillModeForwards dan dihapusOnCompletion menjadi TIDAK, Anda membiarkan animasi tetap berada di layer. Jika Anda memperbaiki delegasi animasi dan mencoba sesuatu seperti:

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

... layer mengembalikan segera pada saat Anda mengeksekusi baris ini. Itu yang ingin kami hindari.

Anda harus memperbaiki properti layer sebelum menghapus animasi dari itu. Coba ini:

- (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
sumber
1

Tampaknya flag yang dihapus OnCompletion diatur ke false dan fillMode diatur ke kCAFillModeForwards juga tidak bekerja untuk saya.

Setelah saya menerapkan animasi baru pada layer, objek animating me-reset ke keadaan awal dan kemudian menjiwai dari keadaan itu. Apa yang harus dilakukan tambahan adalah untuk mengatur properti lapisan model yang diinginkan sesuai dengan properti lapisan presentasinya sebelum mengatur animasi baru seperti:

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

Solusi termudah adalah menggunakan animasi tersirat. Ini akan menangani semua masalah itu untuk Anda:

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

Jika Anda ingin menyesuaikan misalnya durasinya, Anda dapat menggunakan NSAnimationContext:

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

Catatan: Ini hanya diuji pada MacOS.

Awalnya saya tidak melihat animasi apa pun ketika melakukan ini. Masalahnya adalah bahwa lapisan lapisan yang didukung tampilan tidak tersirat bernyawa. Untuk mengatasi ini, pastikan Anda menambahkan layer sendiri (sebelum mengatur tampilan ke layer-didukung).

Contoh cara melakukan ini adalah:

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

Menggunakan self.wantsLayertidak membuat perbedaan dalam pengujian saya, tetapi bisa memiliki beberapa efek samping yang saya tidak tahu.

paxos
sumber
0

Berikut ini contoh dari taman bermain:

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. Buat model animasi
  2. Atur posisi awal animasi (bisa dilewati, itu tergantung pada tata letak layer Anda saat ini)
  3. Atur posisi ujung animasi
  4. Tetapkan durasi animasi
  5. Tunda animasi sebentar
  6. Jangan diatur falseke isRemovedOnCompletion- biarkan Core Animation bersih setelah animasi selesai
  7. Inilah bagian pertama dari triknya - Anda mengatakan kepada Core Animation untuk menempatkan animasi Anda ke posisi awal (Anda atur pada langkah # 2) sebelum animasi bahkan dimulai - perluas mundur dalam waktu
  8. Salin objek animasi yang sudah disiapkan, tambahkan ke layer dan mulai animasi setelah penundaan (Anda atur pada langkah # 5)
  9. Bagian kedua adalah mengatur posisi ujung yang benar dari layer - setelah animasi dihapus, layer Anda akan ditampilkan di tempat yang benar
adnako
sumber