Core Animation ile özel hareket hızı işlevi nasıl oluşturulur?


115

IOS'ta CALayerbir CGPath(QuadCurve) güzel bir şekilde canlandırıyorum . Ancak Apple tarafından sağlanan birkaçından daha ilginç bir hareket hızı işlevi kullanmak istiyorum (EaseIn / EaseOut vb.). Örneğin, bir sıçrama veya elastik işlev.

MediaTimingFunction (bezier) ile aşağıdakileri yapmak mümkündür:

görüntü açıklamasını buraya girin

Ancak daha karmaşık olan zamanlama işlevleri oluşturmak istiyorum . Sorun şu ki, ortam zamanlaması bu efektleri yaratmak için yeterince güçlü olmayan kübik bir bezier gerektiriyor gibi görünüyor:

görüntü açıklamasını buraya girin
(kaynak: sparrow-framework.org )

Kod bu çok sinir bozucu kılan diğer çerçeveler içinde üzerindedir basit yeterince oluşturmak için. Eğrilerin, zaman-konum eğrileri değil, girdi süresini çıktı süresine (Tt eğrisi) eşlediğini unutmayın. Örneğin, easeOutBounce (T) = t yeni döner t . Sonra bu t , hareketi (veya canlandırmamız gereken özelliği) çizmek için kullanılır.

Öyleyse, karmaşık bir gelenek yaratmak istiyorum CAMediaTimingFunctionama bunu nasıl yapacağım veya mümkün olup olmadığı konusunda hiçbir fikrim yok? Herhangi bir alternatif var mı?

DÜZENLE:

İşte adımlarla ilgili somut bir örnek. Çok eğitici :)

  1. Noktanın bir hat boyunca bir nesne animasyon istediğiniz a için b , ama "sıçrama" için yukarıda easeOutBounce eğrisi kullanılarak hattı boyunca hareketini istiyorum. Bu araçlar ondan kesin çizgiyi takip edecek bir etmek b ama hızlandırmak ve mevcut Bezier tabanlı CAMediaTimingFunction kullanılarak mümkün olandan daha karmaşık bir şekilde yavaşlar.

  2. Bu çizgiyi CGPath ile belirtilen herhangi bir gelişigüzel eğri hareketi yapalım. Yine de bu eğri boyunca hareket etmelidir, ancak çizgi örneğindekiyle aynı şekilde hızlanmalı ve yavaşlamalıdır.

Teorik olarak şöyle çalışması gerektiğini düşünüyorum:

Hareket eğrisini bir anahtar kare animasyon hareketi (t) = p olarak tanımlayalım , burada t zaman [0..1], p ise t zamanında hesaplanan konumdur . Dolayısıyla hareket (0) , eğrinin başlangıcındaki konumu döndürür , tam ortayı (0.5) hareket ettirin ve sonunda (1) hareket ettirin . Hareket için t değerlerini sağlamak için bir zamanlama fonksiyonu kullanmak zaman (T) = t bana istediğimi vermelidir. Bir sıçrama etkisi için, zamanlama işlevi zaman (0.8) ve zaman (0.8) için aynı t değerlerini döndürmelidir.(sadece bir örnek). Farklı bir etki elde etmek için zamanlama işlevini değiştirmeniz yeterlidir.

(Evet, ileri geri giden dört çizgi parçası oluşturarak ve birleştirerek çizgi sıçrama yapmak mümkündür, ancak bu gerekli olmamalıdır. Sonuçta, zaman değerlerini konumlara eşleyen basit bir doğrusal işlevdir .)

Umarım burada mantıklıyımdır.


( SO'da sıklıkla olduğu gibi), bu çok eski sorunun artık çok eski cevaplara sahip olduğunun farkında olun. (Son derece dikkate değer: cubic-bezier.com !)
Şişko

Yanıtlar:


48

Bunu buldum:

Sevgiyle Kakao - Temel Animasyonda parametrik ivme eğrileri

Ancak bloklar kullanılarak biraz daha basit ve daha okunaklı hale getirilebileceğini düşünüyorum. Böylece, CAKeyframeAnimation'da şuna benzer bir kategori tanımlayabiliriz:

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

Şimdi kullanım şöyle görünecek:

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

İstediğin kadar basit olmayabileceğini biliyorum, ama bu bir başlangıç.


1
Jesse Crossen'in cevabını aldım ve biraz genişlettim. Örneğin CGPoints'i ve CGSize'ı canlandırmak için kullanabilirsiniz. İOS 7'den itibaren, UIView animasyonlarıyla rastgele zaman işlevlerini de kullanabilirsiniz. En sonuçlarını kontrol edebilirsiniz github.com/jjackson26/JMJParametricAnimation
JJ Jackson

@JJJackson Harika animasyon koleksiyonu! Onları
topladığınız

33

İOS 10'dan, iki yeni zamanlama nesnesini kullanarak özel zamanlama işlevini daha kolay oluşturmak mümkün hale geldi.

1) UICubicTimingParameters , bir hareket hızı işlevi olarak kübik Bézier eğrisini tanımlamaya izin verir .

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)

veya sadece animatörün başlatılmasında kontrol noktaları kullanmak

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) 

Bu harika hizmet , eğrileriniz için kontrol noktaları seçmenize yardımcı olacak.

2) UISpringTimingParameters, geliştiricilerin istenen yay davranışını oluşturmak için sönümleme oranını , kütleyi , sertliği ve başlangıç ​​hızını değiştirmelerine izin verir .

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)

Süre parametresi hala Animator'da sunulmakta, ancak bahar zamanlaması için göz ardı edilecektir.

Bu iki seçenek yeterli değilse, UITimingCurveProvider protokolünü onaylayarak kendi zamanlama eğrinizi de uygulayabilirsiniz .

Daha fazla ayrıntı, farklı zamanlama parametreleriyle animasyonların nasıl oluşturulacağını belgelerde bulabilirsiniz .

Ayrıca, lütfen WWDC 2016'dan UIKit Animasyonlar ve Geçişlerdeki Gelişmeler sunumuna bakın .


Zamanlama işlevlerini kullanma örneklerini iOS10-animasyonlar-demo deposunda bulabilirsiniz .
Olga Konoreva

Lütfen "Bu iki seçenek yeterli değilse, UITimingCurveProvider protokolünü onaylayarak kendi zamanlama eğrinizi de uygulayabilirsiniz." ?
Christian Schnorr

Müthiş! Ve ( ) CoreAnimationsürümü iOS 9'a geri destekleniyor! UISpringTimingParametersCASpringAnimation
Ritmik Fistman

@OlgaKonoreva, cubic-bezier.com hizmeti TAMAMEN FİYATSIZ VE HARİKA . Umarım hala SO kullanıcısısınızdır - size teşekkür etmek için koca kıçlı bir ödül gönderiyor :)
Şişko

@ChristianSchnorr ne cevap için eluding bir yeteneği olduğunu düşünüyorum UITimingCurveProviderkübik temsil etmek sadece ya bir yay animasyon değil, aynı zamanda bir animasyon temsil etmek birleştirir kübik de o ve üzeri bahar UITimingCurveType.composed.
David James

14

Özel bir zamanlama işlevi oluşturmanın bir yolu , CAMediaTimingFunction'daki functionWithControlPoints :::: fabrika yöntemini kullanmaktır (buna karşılık gelen bir initWithControlPoints :::: init yöntemi de vardır). Bunun yaptığı şey, zamanlama fonksiyonunuz için bir Bézier eğrisi oluşturmaktır. Bu rastgele bir eğri değildir, ancak Bézier eğrileri çok güçlü ve esnektir. Kontrol noktalarının asılması biraz pratik gerektirir. Bir ipucu: çoğu çizim programı Bézier eğrileri oluşturabilir. Bunlarla oynamak, size kontrol noktalarıyla temsil ettiğiniz eğri hakkında görsel bir geri bildirim verecektir.

Bu bağlantı elma belgelerine işaret eder. Önceden oluşturulmuş fonksiyonların eğrilerden nasıl oluşturulduğuna dair kısa ama faydalı bir bölüm var.

Düzenleme: Aşağıdaki kod, basit bir sıçrama animasyonunu gösterir. Bunu yapmak için, oluşturulmuş bir zamanlama işlevi ( değerler ve zamanlama NSArray özellikleri) oluşturdum ve animasyonun her segmentine farklı bir zaman uzunluğu ( keytimes özelliği) verdim . Bu şekilde, animasyonlar için daha gelişmiş zamanlama oluşturmak üzere Bézier eğrileri oluşturabilirsiniz. Bu , bu tür animasyonlar hakkında güzel bir örnek kod içeren iyi bir makale.

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

}

Evet bu doğru. İstediğinizi elde etmek için CAKeyframeAnimation'ın değerlerini ve timingFunctions özelliklerini kullanarak birden çok zamanlama işlevini birleştirebilirsiniz. Bir örnek üzerinde çalışacağım ve kodu biraz içine koyacağım.
belirsizlik

Çalışıyor :) Bu yaklaşım için size çabaları ve puan için teşekkürler olabilir teoride faydalı olur ancak gereken her kullanım için özelleştirilmiş olması. Bir zamanlama işlevi olarak tüm animasyonlar için çalışan genel bir çözüm arıyorum. Dürüst olmak gerekirse, çizgileri ve eğrileri bir araya getirmek o kadar iyi görünmüyor. "Kutudaki kriko" örneği de bundan muzdariptir.
Martin Wickman

1
Bir anahtar kare animasyonuna bir değerler dizisi yerine bir CGPathRef belirtebilirsiniz, ancak sonuçta Felz doğrudur - CAKeyframeAnimation ve timingFunctions size ihtiyacınız olan her şeyi verecektir. Bunun neden "her kullanım için özelleştirilmesi" gereken "genel bir çözüm" olmadığını düşündüğünüzden emin değilim. Ana kare animasyonunu fabrika yönteminde veya başka bir şeyde bir kez oluşturursunuz ve daha sonra bu animasyonu herhangi bir katmana istediğiniz kadar defalarca ekleyebilirsiniz. Neden her kullanım için özelleştirilmesi gerekiyor? Bana göre, sizin zamanlama fonksiyonlarının nasıl çalışması gerektiğine dair fikrinizden farklı değil.
Matt Long

1
Beyler, bunlar artık 4 yıl gecikti, ancak functionWithControlPoints :::: tam anlamıyla zıplamalı / esnek animasyonlar yapabilir. Cubic-bezier.com/#.6,1.87,.63,.74
Matt Foley

10

Hâlâ bakıp bakmadığınızdan emin değilsiniz, ancak PRTween , Core Animation'ın size kutudan, en önemlisi, özel zamanlama işlevlerinin ötesine geçme yeteneği açısından oldukça etkileyici görünüyor. Ayrıca, çeşitli web çerçevelerinin sağladığı popüler hareket hızı eğrilerinin tümü olmasa da birçoğuyla birlikte gelir.


2

Bir hızlı versiyonu uygulamasıdır TFAnimation hayranlarıyla demo olduğu günah eğrisi animation.Use TFBasicAnimationgibi CABasicAnimationata dışında timeFunctionbaşka bir blokla timingFunction.

Önemli nokta alt sınıfıdır CAKeyframeAnimationile ve hesaplamak çerçeveleri pozisyon timeFunctioniçinde 1 / 60fpsler aralığı .Montajdan sonra tüm için hesaplanan değeri eklemek valuesarasında CAKeyframeAnimationhiç aralık tarafından ve kez keyTimesde.


2

Birden çok animasyonla bir animasyon grubu oluşturan blok tabanlı bir yaklaşım oluşturdum.

Her bir animasyon, özellik başına 33 farklı parametrik eğriden 1'ini, ilk hızda bir Bozulma zamanlama işlevini veya ihtiyaçlarınıza göre yapılandırılmış özel bir yay kullanabilir.

Grup oluşturulduktan sonra, Görünümde önbelleğe alınır ve animasyonla veya animasyonsuz bir AnimationKey kullanılarak tetiklenebilir. Bir kez tetiklendiğinde animasyon, sunum katmanının değerlerine göre senkronize edilir ve buna göre uygulanır.

Çerçeve burada bulunabilir FlightAnimator

İşte aşağıda bir örnek:

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

Animasyonu tetiklemek için

view.applyAnimation(forKey: AnimationKeys.StageOneAnimationKey)
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.