119 votes

Comment créer une fonction d'assouplissement personnalisée avec Core Animation ?

Je suis en train d'animer un CALayer le long d'un CGPath (QuadCurve) très bien dans iOS. Mais j'aimerais utiliser une fonction d'assouplissement plus intéressante que les quelques fonctions de type fourni par par Apple (EaseIn/EaseOut etc.). Par exemple, une fonction de rebondissement ou d'élasticité.

Ces choses sont possibles avec MediaTimingFunction (bézier) :

enter image description here

Mais j'aimerais créer fonctions de chronométrage qui sont plus complexes. Le problème est que la synchronisation des médias semble nécessiter un bézier cubique qui n'est pas assez puissant pour créer ces effets :

enter image description here
(source : <a href="http://wiki.sparrow-framework.org/_media/manual/transitions.png" rel="noreferrer">sparrow-framework.org </a>)

El code pour créer ce qui précède est assez simple dans d'autres frameworks, ce qui rend ceci très frustrant. Notez que les courbes sont des courbes de correspondance entre le temps d'entrée et le temps de sortie (courbe T-t) et non des courbes de temps-position. Par exemple, easeOutBounce(T) = t renvoie un nouveau t . Alors que t est utilisé pour tracer le mouvement (ou toute autre propriété que nous devons animer).

Donc, je voudrais créer un complexe personnalisé CAMediaTimingFunction mais je n'ai aucune idée de comment faire, ou si c'est même possible ? Existe-t-il des alternatives ?

EDITAR:

Voici un exemple concret en plusieurs étapes. Très pédagogique :)

  1. Je veux animer un objet le long d'une ligne à partir d'un point. a a b mais je veux qu'il "rebondisse" le long de la ligne en utilisant la courbe easeOutBounce ci-dessus. Cela signifie qu'il suivra la ligne exacte de a a b mais accélérera et décélérera d'une manière plus complexe que ce qui est possible en utilisant la CAMediaTimingFunction actuelle basée sur le bézier.

  2. Faisons de cette ligne n'importe quel mouvement de courbe arbitraire spécifié avec CGPath. Elle devrait toujours se déplacer le long de cette courbe, mais elle devrait accélérer et décélérer de la même manière que dans l'exemple de la ligne.

En théorie, je pense que cela devrait fonctionner comme suit :

Décrivons la courbe de mouvement comme une animation par images clés. move(t) = p , donde t est le temps [0..1], p est la position calculée au moment t . Donc déplacer(0) renvoie la position au début de la courbe, déplacer(0.5) le milieu exact et move(1) à la fin. Utilisation d'une fonction de chronométrage temps(T) = t pour fournir le t valeurs pour déplacer devrait me donner ce que je veux. Pour un effet de rebond, la fonction de synchronisation devrait retourner la même chose t valeurs pour temps(0.8) y temps(0.8) (juste un exemple). Il suffit de remplacer la fonction de synchronisation pour obtenir un effet différent.

(Oui, il est possible de faire du rebondissement de ligne en créant et en joignant quatre segments de ligne qui vont et viennent, mais cela ne devrait pas être nécessaire. Après tout, il s'agit juste d'une simple fonction linéaire qui met en correspondance temps valeurs aux positions).

J'espère que je suis logique ici.

0 votes

Soyez conscient que (comme c'est souvent le cas sur le SO), cette une très vieille question dont les réponses sont maintenant très dépassées assurez-vous de faire défiler vers le bas les réponses actuelles étonnantes. (Très remarquable : cubic-bezier.com !)

48voto

Jesse Crossen Points 3244

J'ai trouvé ça :

Cocoa with Love - Courbes d'accélération paramétriques dans Core Animation

Mais je pense que l'on peut le rendre un peu plus simple et plus lisible en utilisant des blocs. Nous pouvons donc définir une catégorie sur CAKeyframeAnimation qui ressemble à quelque chose comme ceci :

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

Maintenant, l'utilisation ressemblera à quelque chose comme ceci :

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

Je sais que ce n'est peut-être pas aussi simple que ce que vous vouliez, mais c'est un début.

1 votes

J'ai repris la réponse de Jesse Crossen, et l'ai un peu développée. Vous pouvez l'utiliser pour animer CGPoints, et CGSize par exemple. Depuis iOS 7, vous pouvez également utiliser des fonctions temporelles arbitraires avec les animations UIView. Vous pouvez consulter les résultats à l'adresse suivante github.com/jjackson26/JMJParametricAnimation

0 votes

@J.J.Jackson Superbe collection d'animations ! Merci de les avoir rassemblées

14voto

Felz Points 5989

Une façon de créer une fonction de temporisation personnalisée est d'utiliser la fonction fonctionWithControlPoints::: : dans CAMediaTimingFunction (il existe également une méthode d'init correspondante initWithControlPoints::: :). Cette méthode permet de créer une courbe de Bézier pour votre fonction de synchronisation. Il ne s'agit pas d'une courbe arbitraire, mais les courbes de Bézier sont très puissantes et flexibles. Il faut un peu de pratique pour s'habituer aux points de contrôle. Un conseil : la plupart des programmes de dessin peuvent créer des courbes de Bézier. En jouant avec celles-ci, vous aurez un retour visuel sur la courbe que vous représentez avec les points de contrôle.

El ce lien pointe vers la documentation d'apple. Il y a une section courte mais utile sur la façon dont les fonctions de pré-construction sont construites à partir de courbes.

Modifier : Le code suivant montre une animation de rebond simple. Pour ce faire, j'ai créé une fonction de timing composée ( valeurs y timing NSArray) et a donné à chaque segment de l'animation une durée différente ( temps forts ). De cette façon, vous pouvez composer des courbes de Bézier pour composer un timing plus sophistiqué pour les animations. Este est un bon article sur ce type d'animations avec un bel exemple de code.

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

}

0 votes

Oui, c'est vrai. Vous pouvez combiner plusieurs fonctions de timing en utilisant les propriétés values et timingFunctions de CAKeyframeAnimation pour obtenir ce que vous voulez. Je vais travailler sur un exemple et mettre le code dans un moment.

0 votes

Merci pour vos efforts, et des points pour avoir essayé :) Cette approche mai fonctionne en théorie, mais il doit être personnalisé pour chaque utilisation. Je cherche une solution générale qui fonctionne pour toutes les animations comme une fonction de synchronisation. Pour être honnête, le fait d'assembler des lignes et des courbes n'a pas l'air très bon. L'exemple du "jack in a box" en souffre également.

1 votes

Vous pouvez spécifier un CGPathRef au lieu d'un tableau de valeurs pour une animation d'image clé, mais en fin de compte, Felz a raison : CAKeyframeAnimation et timingFunctions vous donneront tout ce dont vous avez besoin. Je ne sais pas pourquoi vous pensez que ce n'est pas une "solution générale" qui doit être "personnalisée pour chaque utilisation". Vous construisez l'animation des images clés une fois dans une méthode d'usine ou autre et vous pouvez ensuite ajouter cette animation à n'importe quel calque autant de fois que vous le souhaitez. Pourquoi faudrait-il la personnaliser pour chaque utilisation ? Il me semble que ce n'est pas différent de votre idée de la façon dont les fonctions de synchronisation devraient fonctionner.

10voto

LucasTizma Points 5452

Je ne sais pas si vous cherchez encore, mais PRTween semble assez impressionnant en termes de capacité à aller au-delà de ce que Core Animation vous offre d'emblée, notamment en ce qui concerne les fonctions de synchronisation personnalisées. Il est également fourni avec de nombreuses courbes d'assouplissement, si ce n'est toutes, que proposent les différents frameworks Web.

4voto

J.J. Jackson Points 21

J'ai repris la réponse de Jesse Crossen, et l'ai un peu développée. Vous pouvez l'utiliser pour animer des CGPoints, CGSize, Par exemple à partir de iOS 7, vous pouvez également utiliser des fonctions de temps arbitraires avec les animations UIView.

Vous pouvez consulter les résultats à l'adresse suivante https://github.com/jjackson26/JMJParametricAnimation

3voto

iPatel Points 15121

Je sais que cette question a une réponse très utile, mais j'ai mis un lien très important qui indique exactement ce que je veux dire. OP Veilleurs

https://github.com/NachoSoto/NSBKeyframeAnimation

Dans ce lien, la plupart des animations qui sont décrites dans la section OP's L'image est couverte.

Consultez ce lien et trouvez la source du code. C'est très utile pour les débutants.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X