Wie erstelle ich eine benutzerdefinierte Beschleunigungsfunktion mit Core Animation?

115

Ich animiere eine CALayerentlang einer CGPath(QuadCurve) ganz gut in iOS. Aber ich möchte eine interessantere Beschleunigungsfunktion als die wenigen verwenden , zur Verfügung gestellt von Apple (easeIn / easeOut usw.). Zum Beispiel eine Sprungkraft oder eine elastische Funktion.

Diese Dinge können mit MediaTimingFunction (bezier) gemacht werden:

Geben Sie hier die Bildbeschreibung ein

Aber ich möchte Timing-Funktionen erstellen , die komplexer sind. Das Problem ist, dass für das Media-Timing ein kubischer Bezier erforderlich zu sein scheint, der nicht leistungsfähig genug ist, um diese Effekte zu erzielen:

Geben Sie hier die Bildbeschreibung ein
(Quelle: sparrow-framework.org )

Der Code zum Erstellen des oben genannten ist in anderen Frameworks einfach genug, was dies sehr frustrierend macht. Beachten Sie, dass die Kurven die Eingabezeit der Ausgabezeit (Tt-Kurve) und nicht den Zeitpositionskurven zuordnen. Zum Beispiel gibt easOutBounce (T) = t ein neues t zurück . Dann wird dieses t verwendet, um die Bewegung zu zeichnen (oder welche Eigenschaft wir animieren sollten).

Ich möchte also einen komplexen benutzerdefinierten Benutzer erstellen CAMediaTimingFunction, habe aber keine Ahnung, wie das geht oder ob es überhaupt möglich ist. Gibt es Alternativen?

BEARBEITEN:

Hier ist ein konkretes Beispiel für Schritte. Sehr lehrreich :)

  1. Ich möchte ein Objekt entlang einer Linie von Punkt a nach b animieren , aber ich möchte, dass es seine Bewegung entlang der Linie mithilfe der obigen easyOutBounce-Kurve "abprallt". Dies bedeutet, dass es der exakten Linie von a nach b folgt , jedoch auf komplexere Weise beschleunigt und verzögert, als dies mit der aktuellen bezier-basierten CAMediaTimingFunction möglich ist.

  2. Machen wir diese Linie zu einer beliebigen Kurvenbewegung, die mit CGPath angegeben wurde. Es sollte sich immer noch entlang dieser Kurve bewegen, aber es sollte auf die gleiche Weise wie im Linienbeispiel beschleunigen und abbremsen.

Theoretisch denke ich, dass es so funktionieren sollte:

Beschreiben wir die Bewegungskurve als Keyframe-Animationsbewegung (t) = p , wobei t die Zeit [0..1] und p die zum Zeitpunkt t berechnete Position ist . So bewegt (0) gibt die Position am Anfang der Kurve, bewegt (0,5) die exakten Mitte und Bewegung (1) am Ende. Die Verwendung einer Zeitfunktionszeit (T) = t zur Bereitstellung der t- Werte für die Bewegung sollte mir das geben, was ich will. Für einen Bouncing-Effekt sollte die Timing-Funktion die gleichen t- Werte für Zeit (0,8) und Zeit (0,8) zurückgeben.(nur ein Beispiel). Ersetzen Sie einfach die Timing-Funktion, um einen anderen Effekt zu erzielen.

(Ja, es ist möglich , Line-Prellen durch das Erstellen und Verbinden vier Liniensegmente zu tun , die hin und her geht, aber das sollte nicht notwendig sein. Denn es ist nur eine einfache lineare Funktion , die Karten Zeitwerte zu Positionen.)

Ich hoffe ich mache hier Sinn.

Martin Wickman
quelle
Beachten Sie , dass diese sehr alte Frage (wie dies bei SO häufig der Fall ist) jetzt sehr veraltete Antworten enthält . Scrollen Sie unbedingt zu den aktuellen Antworten. (Sehr bemerkenswert: cubic-bezier.com !)
Fattie

Antworten:

48

Ich habe das gefunden:

Kakao mit Liebe - Parametrische Beschleunigungskurven in der Kernanimation

Aber ich denke, es kann durch die Verwendung von Blöcken ein wenig einfacher und lesbarer gemacht werden. So können wir in CAKeyframeAnimation eine Kategorie definieren, die ungefähr so ​​aussieht:

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

Jetzt sieht die Verwendung ungefähr so ​​aus:

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

Ich weiß, dass es vielleicht nicht ganz so einfach ist, wie Sie es wollten, aber es ist ein Anfang.

Jesse Crossen
quelle
1
Ich nahm die Antwort von Jesse Crossen und erweiterte sie ein wenig. Sie können damit beispielsweise CGPoints und CGSize animieren. Ab iOS 7 können Sie auch beliebige Zeitfunktionen mit UIView-Animationen verwenden. Sie können die Ergebnisse unter github.com/jjackson26/JMJParametricAnimation
JJ Jackson
@JJJackson Tolle Sammlung von Animationen! Danke, dass du sie
gesammelt hast
33

Ab iOS 10 war es möglich, mithilfe von zwei neuen Timing-Objekten eine benutzerdefinierte Timing-Funktion zu erstellen.

1) UICubicTimingParameters ermöglicht cubic definieren Bézier - Kurve als eine Beschleunigungsfunktion.

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)

oder einfach Kontrollpunkte bei der Animatorinitialisierung verwenden

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) 

Dieser großartige Service hilft Ihnen bei der Auswahl von Kontrollpunkten für Ihre Kurven.

2) Mit UISpringTimingParameters können Entwickler das Dämpfungsverhältnis , die Masse , die Steifigkeit und die Anfangsgeschwindigkeit ändern , um das gewünschte Federverhalten zu erzielen .

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)

Der Dauer-Parameter wird weiterhin in Animator angezeigt, wird jedoch für den Federzeitpunkt ignoriert.

Wenn diese beiden Optionen nicht ausreichen, können Sie auch Ihre eigene Zeitkurve implementieren, indem Sie das UITimingCurveProvider- Protokoll bestätigen.

Weitere Informationen zum Erstellen von Animationen mit unterschiedlichen Timing-Parametern finden Sie in der Dokumentation .

Weitere Informationen finden Sie in der Präsentation zu Fortschritten bei UIKit-Animationen und -Übergängen auf der WWDC 2016.

Olga Konoreva
quelle
Beispiele für die Verwendung von Timing-Funktionen finden Sie im iOS10-Animations-Demo-Repository .
Olga Konoreva
Können Sie bitte näher auf "Wenn diese beiden Optionen nicht ausreichen, können Sie auch Ihre eigene Zeitkurve implementieren, indem Sie das UITimingCurveProvider-Protokoll bestätigen." ?
Christian Schnorr
Genial! Und die CoreAnimationVersion von UISpringTimingParameters( CASpringAnimation) wird wieder auf iOS 9 unterstützt!
Rhythmic Fistman
@OlgaKonoreva, der Dienst cubic-bezier.com ist äußerst unbezahlbar und wunderbar . Ich hoffe, Sie sind immer noch ein SO-Benutzer - Sie erhalten eine fette Prämie, um sich zu bedanken :)
Fattie
@ChristianSchnorr Ich denke, die Antwort entgeht der Fähigkeit von a UITimingCurveProvider, nicht nur eine kubische oder eine Frühlingsanimation darzustellen, sondern auch eine Animation darzustellen, die sowohl kubisch als auch frühlingshaft kombiniert UITimingCurveType.composed.
David James
14

Ein Weg , um eine individuelle Zeitsteuerungsfunktion zu schaffen , ist die Verwendung von functionWithControlPoints :::: Fabrikmethode in CAMediaTimingFunction (es gibt eine entsprechende initWithControlPoints :::: Init - Methode als auch). Dadurch wird eine Bézier-Kurve für Ihre Timing-Funktion erstellt. Es ist keine willkürliche Kurve, aber Bézier-Kurven sind sehr leistungsfähig und flexibel. Es braucht ein wenig Übung, um die Kontrollpunkte in den Griff zu bekommen. Ein Tipp: Die meisten Zeichenprogramme können Bézier-Kurven erstellen. Wenn Sie mit diesen spielen, erhalten Sie ein visuelles Feedback zu der Kurve, die Sie mit den Kontrollpunkten darstellen.

Der Link verweist auf Apfel-Dokumentation. Es gibt einen kurzen, aber nützlichen Abschnitt darüber, wie die Pre-Build-Funktionen aus Kurven aufgebaut sind.

Bearbeiten: Der folgende Code zeigt eine einfache Bounce-Animation. Zu diesem Zweck habe ich eine zusammengesetzte Timing-Funktion ( Werte und Timing- NSArray-Eigenschaften) erstellt und jedem Segment der Animation eine andere Zeitlänge zugewiesen ( keytimes- Eigenschaft). Auf diese Weise können Sie Bézier-Kurven erstellen, um ein ausgefeilteres Timing für Animationen zu erstellen. Dies ist ein guter Artikel über diese Art von Animationen mit einem schönen Beispielcode.

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

}
fsaint
quelle
Ja, das ist wahr. Sie können mehrere Timing-Funktionen mit den Werten und TimingFunctions-Eigenschaften von CAKeyframeAnimation kombinieren, um das zu erreichen, was Sie möchten. Ich werde ein Beispiel ausarbeiten und den Code ein wenig einfügen.
Heiliger
Vielen Dank für Ihre Bemühungen und Punkte für den Versuch :) Dieser Ansatz mag theoretisch funktionieren, muss jedoch für jede Verwendung angepasst werden. Ich suche nach einer allgemeinen Lösung, die für alle Animationen als Timing-Funktion funktioniert. Um ehrlich zu sein, sieht es nicht so gut aus, Linien und Kurven zusammenzusetzen. Darunter leidet auch das Beispiel "Jack in a Box".
Martin Wickman
1
Sie können für eine Keyframe-Animation ein CGPathRef anstelle eines Array von Werten angeben, aber am Ende ist Felz korrekt - CAKeyframeAnimation und TimingFunctions bieten Ihnen alles, was Sie brauchen. Ich bin mir nicht sicher, warum Sie denken, dass dies keine "allgemeine Lösung" ist, die "für jede Verwendung angepasst" werden muss. Sie erstellen die Keyframe-Animation einmal in einer Factory-Methode oder so etwas und können diese Animation dann beliebig oft zu einer beliebigen Ebene hinzufügen. Warum sollte es für jede Verwendung angepasst werden müssen? Mir scheint, es unterscheidet sich nicht von Ihrer Vorstellung, wie Timing-Funktionen funktionieren sollten.
Matt Long
1
Oh Leute, das ist jetzt 4 Jahre zu spät, aber wenn functionWithControlPoints :::: total hüpfende / federnde Animationen machen kann. Verwenden Sie es in Verbindung mit cubic-bezier.com/#.6,1.87,.63,.74
Matt Foley
10

Ich bin mir nicht sicher, ob Sie noch suchen, aber PRTween sieht ziemlich beeindruckend aus, da es über das hinausgeht, was Core Animation Ihnen sofort bietet, insbesondere benutzerdefinierte Timing-Funktionen. Es enthält auch viele - wenn nicht alle - der beliebten Lockerungskurven, die verschiedene Web-Frameworks bieten.

CIFilter
quelle
2

Eine Implementierung in einer schnellen Version ist TFAnimation. Die Demo ist eine Sin- Kurven-Animation. Verwenden SieTFBasicAnimation genau wie CABasicAnimationaußer timeFunctionmit einem anderen Block als zuweisen timingFunction.

Der Schlüsselpunkt ist die Unterklasse CAKeyframeAnimationund die Berechnung der Frame- Position timeFunctionin 1 / 60fpsIntervallen von s. Fügen Sie schließlich auch den berechneten Wert zu valuesvon CAKeyframeAnimationund die Zeiten nach Intervallen keyTimeszu hinzu.

Xingxing
quelle
2

Ich habe einen blockbasierten Ansatz erstellt, der eine Animationsgruppe mit mehreren Animationen generiert.

Jede Animation kann pro Eigenschaft 1 von 33 verschiedenen Parameterkurven, eine Decay-Timing-Funktion mit Anfangsgeschwindigkeit oder eine benutzerdefinierte Feder verwenden, die für Ihre Anforderungen konfiguriert ist.

Sobald die Gruppe generiert wurde, wird sie in der Ansicht zwischengespeichert und kann mit einem AnimationKey mit oder ohne Animation ausgelöst werden. Nach dem Auslösen wird die Animation entsprechend mit den Werten der Präsentationsebene synchronisiert und entsprechend angewendet.

Das Framework finden Sie hier FlightAnimator

Hier ist ein Beispiel unten:

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

Zum Auslösen der Animation

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