Bagaimana cara membuat fungsi easing kustom dengan Core Animation?

115

Saya menganimasikan CALayersepanjang CGPath(QuadCurve) dengan cukup baik di iOS. Tapi saya ingin menggunakan fungsi easing yang lebih menarik daripada yang disediakan oleh Apple (EaseIn / EaseOut dll). Misalnya, fungsi pantulan atau elastis.

Hal-hal berikut dapat dilakukan dengan MediaTimingFunction (bezier):

masukkan deskripsi gambar di sini

Tapi saya ingin membuat fungsi pengaturan waktu yang lebih kompleks. Masalahnya adalah bahwa pengaturan waktu media tampaknya memerlukan bezier kubik yang tidak cukup kuat untuk menciptakan efek berikut:

masukkan deskripsi gambar di sini
(sumber: sparrow-framework.org )

The kode untuk membuat cukup sederhana di atas adalah dalam framework lain, yang membuat sangat frustasi ini. Perhatikan bahwa kurva memetakan waktu masukan ke waktu keluaran (kurva Tt) dan bukan kurva waktu-posisi. Misalnya, easyOutBounce (T) = t mengembalikan t baru . Kemudian t itu digunakan untuk memplot gerakan (atau properti apa pun yang harus kita animasikan).

Jadi, saya ingin membuat kebiasaan yang rumit CAMediaTimingFunctiontetapi saya tidak tahu bagaimana melakukannya, atau apakah itu mungkin? Apakah ada alternatif lain?

EDIT:

Berikut adalah contoh konkret untuk langkah. Sangat mendidik :)

  1. Saya ingin menganimasikan objek di sepanjang garis dari titik a ke b , tetapi saya ingin objek "memantulkan" pergerakannya di sepanjang garis menggunakan kurva kemudahanOutBounce di atas. Ini berarti itu akan mengikuti garis yang tepat dari a ke b , tetapi akan mempercepat dan melambat dengan cara yang lebih kompleks daripada yang mungkin menggunakan CAMediaTimingFunction berbasis bezier saat ini.

  2. Mari kita buat garis itu setiap gerakan kurva arbitrer yang ditentukan dengan CGPath. Itu harus tetap bergerak di sepanjang kurva itu, tetapi itu harus dipercepat dan diperlambat dengan cara yang sama seperti pada contoh garis.

Secara teori saya pikir itu harus bekerja seperti ini:

Mari kita gambarkan kurva pergerakan sebagai animasi keyframe move (t) = p , dimana t adalah waktu [0..1], p adalah posisi yang dihitung pada waktu t . Jadi move (0) mengembalikan posisi pada awal kurva, gerakkan (0,5) tepat di tengah dan gerakkan (1) di akhir. Menggunakan fungsi waktu waktu (T) = t untuk memberikan nilai t untuk memindahkan harus memberi saya apa yang saya inginkan. Untuk efek pantulan, fungsi waktu harus mengembalikan nilai t yang sama untuk waktu (0.8) dan waktu (0.8)(hanya contoh). Ganti saja fungsi pengaturan waktu untuk mendapatkan efek yang berbeda.

(Ya, memantulkan garis mungkin dilakukan dengan membuat dan menggabungkan empat segmen garis yang bolak-balik, tetapi itu tidak perlu. Bagaimanapun, ini hanya fungsi linier sederhana yang memetakan nilai waktu ke posisi.)

Saya harap saya masuk akal di sini.

Martin Wickman
sumber
Sadarilah bahwa (seperti yang sering terjadi pada SO), pertanyaan yang sangat lama ini sekarang memiliki jawaban yang sangat kuno .. pastikan untuk menggulir ke bawah ke jawaban yang menakjubkan saat ini. (Sangat terkenal: cubic-bezier.com !)
Fattie

Jawaban:

48

Aku menemukan ini:

Cocoa with Love - Kurva akselerasi parametrik di Core Animation

Tapi saya pikir itu bisa dibuat lebih sederhana dan lebih mudah dibaca dengan menggunakan blok. Jadi kita dapat menentukan kategori di CAKeyframeAnimation yang terlihat seperti ini:

CAKeyframeAnimation + Parametric.h:

// this should be a function that takes a time value between 
//  0.0 and 1.0 (where 0.0 is the beginning of the animation
//  and 1.0 is the end) and returns a scale factor where 0.0
//  would produce the starting value and 1.0 would produce the
//  ending value
typedef double (^KeyframeParametricBlock)(double);

@interface CAKeyframeAnimation (Parametric)

+ (id)animationWithKeyPath:(NSString *)path 
      function:(KeyframeParametricBlock)block
      fromValue:(double)fromValue
      toValue:(double)toValue;

CAKeyframeAnimation + Parametric.m:

@implementation CAKeyframeAnimation (Parametric)

+ (id)animationWithKeyPath:(NSString *)path 
      function:(KeyframeParametricBlock)block
      fromValue:(double)fromValue
      toValue:(double)toValue {
  // get a keyframe animation to set up
  CAKeyframeAnimation *animation = 
    [CAKeyframeAnimation animationWithKeyPath:path];
  // break the time into steps
  //  (the more steps, the smoother the animation)
  NSUInteger steps = 100;
  NSMutableArray *values = [NSMutableArray arrayWithCapacity:steps];
  double time = 0.0;
  double timeStep = 1.0 / (double)(steps - 1);
  for(NSUInteger i = 0; i < steps; i++) {
    double value = fromValue + (block(time) * (toValue - fromValue));
    [values addObject:[NSNumber numberWithDouble:value]];
    time += timeStep;
  }
  // we want linear animation between keyframes, with equal time steps
  animation.calculationMode = kCAAnimationLinear;
  // set keyframes and we're done
  [animation setValues:values];
  return(animation);
}

@end

Sekarang penggunaannya akan terlihat seperti ini:

// define a parametric function
KeyframeParametricBlock function = ^double(double time) {
  return(1.0 - pow((1.0 - time), 2.0));
};

if (layer) {
  [CATransaction begin];
    [CATransaction 
      setValue:[NSNumber numberWithFloat:2.5]
      forKey:kCATransactionAnimationDuration];

    // make an animation
    CAAnimation *drop = [CAKeyframeAnimation 
      animationWithKeyPath:@"position.y"
      function:function fromValue:30.0 toValue:450.0];
    // use it
    [layer addAnimation:drop forKey:@"position"];

  [CATransaction commit];
}

Saya tahu ini mungkin tidak sesederhana yang Anda inginkan, tetapi ini adalah permulaan.

Jesse Crossen
sumber
1
Saya menerima tanggapan Jesse Crossen, dan mengembangkannya sedikit. Anda dapat menggunakannya untuk menganimasikan CGPoints, dan CGSize misalnya. Mulai iOS 7, Anda juga dapat menggunakan fungsi waktu arbitrer dengan animasi UIView. Anda dapat melihat hasilnya di github.com/jjackson26/JMJParametricAnimation
JJ Jackson
@JJJackson Koleksi animasi yang luar biasa! Terima kasih telah mengumpulkannya
Codey
33

Dari iOS 10 menjadi mungkin untuk membuat fungsi pengaturan waktu kustom lebih mudah menggunakan dua objek pengaturan waktu baru.

1) UICubicTimingParameters memungkinkan untuk menentukan kurva Bézier kubik sebagai fungsi easing.

let cubicTimingParameters = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.25, y: 0.1), controlPoint2: CGPoint(x: 0.25, y: 1))
let animator = UIViewPropertyAnimator(duration: 0.3, timingParameters: cubicTimingParameters)

atau cukup menggunakan titik kontrol pada inisialisasi animator

let controlPoint1 = CGPoint(x: 0.25, y: 0.1)
let controlPoint2 = CGPoint(x: 0.25, y: 1)
let animator = UIViewPropertyAnimator(duration: 0.3, controlPoint1: controlPoint1, controlPoint2: controlPoint2) 

Layanan luar biasa ini akan membantu memilih titik kontrol untuk lekuk tubuh Anda.

2) UISpringTimingParameters memungkinkan pengembang memanipulasi rasio redaman , massa , kekakuan , dan kecepatan awal untuk menciptakan perilaku pegas yang diinginkan.

let velocity = CGVector(dx: 1, dy: 0)
let springParameters = UISpringTimingParameters(mass: 1.8, stiffness: 330, damping: 33, initialVelocity: velocity)
let springAnimator = UIViewPropertyAnimator(duration: 0.0, timingParameters: springParameters)

Parameter durasi masih disajikan di Animator, tetapi akan diabaikan untuk pengaturan waktu musim semi.

Jika kedua opsi ini tidak cukup, Anda juga dapat menerapkan kurva waktu Anda sendiri dengan mengonfirmasi ke protokol UITimingCurveProvider .

Lebih lengkapnya, cara membuat animasi dengan parameter timing yang berbeda bisa Anda temukan di dokumentasi .

Lihat juga presentasi Kemajuan Animasi dan Transisi UIKit dari WWDC 2016.

Olga Konoreva
sumber
Anda dapat menemukan contoh penggunaan fungsi Pengaturan waktu di repositori iOS10-animasi-demo .
Olga Konoreva
Bisakah Anda menjelaskan lebih lanjut tentang "Jika kedua opsi ini tidak cukup, Anda juga dapat menerapkan kurva waktu Anda sendiri dengan mengonfirmasi ke protokol UITimingCurveProvider." ?
Christian Schnorr
Hebat! Dan CoreAnimationversi UISpringTimingParameters( CASpringAnimation) didukung kembali ke iOS 9!
Rhythmic Fistman
@OlgaKonoreva, layanan cubic-bezier.com SANGAT HARGA DAN LUAR BIASA . Harap Anda masih menjadi pengguna SO - mengirimi Anda hadiah besar untuk mengucapkan terima kasih :)
Fattie
@ChristianSchnorr Saya rasa jawabannya adalah kemampuan a UITimingCurveProvideruntuk tidak hanya mewakili kubik atau animasi pegas, tetapi juga untuk mewakili animasi yang menggabungkan kubik dan pegas melalui UITimingCurveType.composed.
David James
14

Cara untuk membuat fungsi pengaturan waktu kustom adalah dengan menggunakan metode functionWithControlPoints :::: pabrik di CAMediaTimingFunction (ada metode initWithControlPoints :::: init yang sesuai juga). Apa yang dilakukannya adalah membuat kurva Bézier untuk fungsi pengaturan waktu Anda. Ini bukan kurva yang berubah-ubah, tetapi kurva Bézier sangat kuat dan fleksibel. Dibutuhkan sedikit latihan untuk memahami poin kontrol. Tip: kebanyakan program menggambar dapat membuat kurva Bézier. Bermain dengan itu akan memberi Anda umpan balik visual pada kurva yang Anda wakili dengan titik kontrol.

The link ini poin untuk dokumentasi apel. Ada bagian singkat tapi berguna tentang bagaimana fungsi pra-bangun dibangun dari kurva.

Edit: Kode berikut menunjukkan animasi pantulan sederhana. Untuk melakukannya, saya membuat fungsi pengaturan waktu ( nilai dan pengaturan waktu properti NSArray) dan memberi setiap segmen animasi panjang waktu yang berbeda ( properti keytimes ). Dengan cara ini Anda dapat membuat kurva Bézier untuk membuat pengaturan waktu yang lebih canggih untuk animasi. Ini adalah artikel bagus tentang jenis animasi ini dengan kode contoh yang bagus.

- (void)viewDidLoad {
    UIView *v = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 50.0, 50.0)];

    v.backgroundColor = [UIColor redColor];
    CGFloat y = self.view.bounds.size.height;
    v.center = CGPointMake(self.view.bounds.size.width/2.0, 50.0/2.0);
    [self.view addSubview:v];

    //[CATransaction begin];

    CAKeyframeAnimation * animation; 
    animation = [CAKeyframeAnimation animationWithKeyPath:@"position.y"]; 
    animation.duration = 3.0; 
    animation.removedOnCompletion = NO;
    animation.fillMode = kCAFillModeForwards;

    NSMutableArray *values = [NSMutableArray array];
    NSMutableArray *timings = [NSMutableArray array];
    NSMutableArray *keytimes = [NSMutableArray array];

    //Start
    [values addObject:[NSNumber numberWithFloat:25.0]];
    [timings addObject:GetTiming(kCAMediaTimingFunctionEaseIn)];
    [keytimes addObject:[NSNumber numberWithFloat:0.0]];


    //Drop down
    [values addObject:[NSNumber numberWithFloat:y]];
    [timings addObject:GetTiming(kCAMediaTimingFunctionEaseOut)];
    [keytimes addObject:[NSNumber numberWithFloat:0.6]];


    // bounce up
    [values addObject:[NSNumber numberWithFloat:0.7 * y]];
    [timings addObject:GetTiming(kCAMediaTimingFunctionEaseIn)];
    [keytimes addObject:[NSNumber numberWithFloat:0.8]];


    // fihish down
    [values addObject:[NSNumber numberWithFloat:y]];
    [keytimes addObject:[NSNumber numberWithFloat:1.0]];
    //[timings addObject:GetTiming(kCAMediaTimingFunctionEaseIn)];



    animation.values = values;
    animation.timingFunctions = timings;
    animation.keyTimes = keytimes;

    [v.layer addAnimation:animation forKey:nil];   

    //[CATransaction commit];

}
pingsan
sumber
Ya itu benar. Anda dapat menggabungkan beberapa fungsi pengaturan waktu menggunakan properti values ​​dan timingFunctions dari CAKeyframeAnimation untuk mencapai apa yang Anda inginkan. Saya akan membuat contoh dan memasukkan kode sedikit.
pingsan
Terima kasih atas upaya Anda, dan poin untuk mencoba :) Pendekatan itu mungkin bekerja secara teori, tetapi perlu disesuaikan untuk setiap penggunaan. Saya mencari solusi umum yang berfungsi untuk semua animasi sebagai fungsi pengaturan waktu. Sejujurnya tidak terlihat bagus menyatukan garis dan kurva. Contoh "dongkrak dalam kotak" juga mengalami hal ini.
Martin Wickman
1
Anda dapat menentukan CGPathRef alih-alih larik nilai ke animasi keyframe, tetapi pada akhirnya Felz benar - CAKeyframeAnimation dan timingFunctions akan memberi Anda semua yang Anda butuhkan. Saya tidak yakin mengapa Anda berpikir itu bukan "solusi umum" yang harus "disesuaikan untuk setiap penggunaan". Anda membangun animasi keyframe sekali dalam metode pabrik atau sesuatu dan Anda kemudian dapat menambahkan animasi itu ke lapisan mana pun berulang kali sebanyak yang Anda suka. Mengapa perlu disesuaikan untuk setiap penggunaan? Menurut saya itu tidak berbeda dengan gagasan Anda tentang bagaimana fungsi pengaturan waktu seharusnya bekerja.
Matt Long
1
Ya ampun, ini sekarang sudah terlambat 4 tahun, tetapi jika functionWithControlPoints :::: benar-benar dapat melakukan animasi bouncy / springy. Gunakan dalam hubungannya dengan cubic-bezier.com/#.6,1.87,.63,74
Matt Foley
10

Tidak yakin apakah Anda masih mencari, tetapi PRTween terlihat cukup mengesankan dalam hal kemampuannya untuk melampaui apa yang Core Animation berikan di luar kotak, terutama, fungsi pengaturan waktu khusus. Itu juga dikemas dengan banyak — jika tidak semua — kurva easing populer yang disediakan oleh berbagai kerangka web.

CIFilter
sumber
2

Implementasi versi cepat adalah TFAnimation . Demo adalah animasi kurva sin . GunakanTFBasicAnimation seperti CABasicAnimationkecuali assign timeFunctiondengan blok selain timingFunction.

Kuncinya adalah subclass CAKeyframeAnimationdan menghitung posisi frame dengan timeFunctiondalam 1 / 60fpsinterval s. Setelah semua tambahkan semua nilai yang dihitung ke valuesdari CAKeyframeAnimationdan waktu dengan interval keyTimesjuga.

Xingxing
sumber
2

Saya membuat pendekatan berbasis blok, yang menghasilkan grup animasi, dengan banyak animasi.

Setiap animasi, per properti, dapat menggunakan 1 dari 33 kurva parametrik yang berbeda, fungsi pengaturan waktu Peluruhan dengan kecepatan awal, atau pegas khusus yang dikonfigurasi sesuai kebutuhan Anda.

Setelah grup dibuat, itu di-cache di Tampilan, dan dapat dipicu menggunakan AnimationKey, dengan atau tanpa animasi. Setelah dipicu, animasi akan disinkronkan sesuai dengan nilai lapisan presentasi, dan diterapkan sesuai dengan itu.

Kerangka tersebut dapat ditemukan di sini FlightAnimator

Berikut contohnya di bawah ini:

struct AnimationKeys {
    static let StageOneAnimationKey  = "StageOneAnimationKey"
    static let StageTwoAnimationKey  = "StageTwoAnimationKey"
}

...

view.registerAnimation(forKey: AnimationKeys.StageOneAnimationKey, maker:  { (maker) in

    maker.animateBounds(toValue: newBounds,
                     duration: 0.5,
                     easingFunction: .EaseOutCubic)

    maker.animatePosition(toValue: newPosition,
                     duration: 0.5,
                     easingFunction: .EaseOutCubic)

    maker.triggerTimedAnimation(forKey: AnimationKeys.StageTwoAnimationKey,
                           onView: self.secondaryView,
                           atProgress: 0.5, 
                           maker: { (makerStageTwo) in

        makerStageTwo.animateBounds(withDuration: 0.5,
                             easingFunction: .EaseOutCubic,
                             toValue: newSecondaryBounds)

        makerStageTwo.animatePosition(withDuration: 0.5,
                             easingFunction: .EaseOutCubic,
                             toValue: newSecondaryCenter)
     })                    
})

Untuk memicu animasi

view.applyAnimation(forKey: AnimationKeys.StageOneAnimationKey)
AntonTheDev
sumber