web-dev-qa-db-fra.com

CoreAnimation - Les fondus d'opacité d'animation ne fonctionnent pas

J'essaie de créer une CoreAnimation assez simple à utiliser dans un AVComposition. Mon objectif est de créer une variable CALayer qui, par l’intermédiaire de différentes sous-couches, permet d’affiner et de masquer un titre, puis de masquer les images. Un diaporama, fondamentalement. Ceci est exporté vers un .mov en utilisant AVAssetWriter

Avec l'aide de AVEditDemo WWDC 2011, j'ai pu faire apparaître un titre et des images. Le problème est qu'ils sont tous à l'écran en même temps!

J'ai créé chaque calque avec une opacité de 0.0. J'ai ensuite ajouté un CABasicAnimation pour les atténuer de 0.0 à 1.0, en utilisant le code suivant:

CABasicAnimation *fadeInAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
fadeInAnimation.fromValue = [NSNumber numberWithFloat:0.0];
fadeInAnimation.toValue = [NSNumber numberWithFloat:1.0];
fadeInAnimation.additive = NO;
fadeInAnimation.removedOnCompletion = YES;
fadeInAnimation.beginTime = 1.0;
fadeInAnimation.duration = 1.0;
fadeInAnimation.fillMode = kCAFillModeForwards;
[titleLayer addAnimation:fadeInAnimation forKey:nil];

Le problème semble être la propriété 'beginTime'. Le "1.0" est censé être un délai, il commence donc 1 seconde après le début de l'animation. Cependant, il apparaît tout de suite à l'écran. Une animation en fondu

L'inverse de ce code, pour le fondu sortant, change simplement les valeurs fromValue à 1.0 et toValue à 0.0. Il a un temps initial de 4.0 et fonctionne parfaitement.

J'utilise les éléments suivants pour créer le animatedTitleLayer:

CATextLayer *titleLayer = [CATextLayer layer];
titleLayer.string =self.album.title;
titleLayer.font = @"Helvetica";
titleLayer.fontSize = videoSize.height / 6;
titleLayer.alignmentMode = kCAAlignmentCenter;
titleLayer.bounds = CGRectMake(0, 0, videoSize.width, videoSize.height / 6);
titleLayer.foregroundColor = [[UIColor redColor]CGColor];
titleLayer.opacity = 0.0;

Les animations de fondu d’image ont un début d’après 5 secondes. Comme le titre, leurs animations en fondu fonctionnent bien.

Toute aide serait grandement appréciée!

À votre santé!

MODIFIER

Les réponses étaient toutes utiles, mais j'ai finalement découvert qu'une seule animation pouvait être ajoutée à un CALayer. L'animation de fondu fonctionnait, car c'était la dernière ajoutée. 

J'ai ensuite essayé un groupe CAAnimation, mais cela ne fonctionnait pas car je modifiais le même chemin de valeur de clé.

J'ai donc compris qu'un CAKeyframeAnimation est le meilleur pour cela. Seulement, j'ai aussi des difficultés avec ça! Le code est en train de disparaître, mais il ne disparaît pas. J'ai essayé divers fillMode, changé la durée, etc. Je ne peux pas le faire fonctionner!

Voici mon code:

    CAKeyframeAnimation *fadeInAndOut = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
    fadeInAndOut.duration = 5.0;
    fadeInAndOut.autoreverses = NO;
    fadeInAndOut.keyTimes = [NSArray arrayWithObjects:  [NSNumber numberWithFloat:0.0],
                                                            [NSNumber numberWithFloat:1.0],
                                                    [NSNumber numberWithFloat:4.0], 
                                                    [NSNumber numberWithFloat:5.0], nil];

    fadeInAndOut.values = [NSArray arrayWithObjects:    [NSNumber numberWithFloat:0.0],
                                                    [NSNumber numberWithFloat:1.0],
                                                    [NSNumber numberWithFloat:1.0], 
                                                    [NSNumber numberWithFloat:0.0], nil];
    fadeInAndOut.beginTime = 1.0;
    fadeInAndOut.removedOnCompletion = NO;
    fadeInAndOut.fillMode = kCAFillModeBoth;
    [titleLayer addAnimation:fadeInAndOut forKey:nil]; 
20
gamblor87

Je suis confronté au même problème et le problème est le suivant: une couche ne peut pas contenir de fondu en entrée et en sortie. Vous pouvez donc ajouter l’autre animation au calque parent comme je l’ai fait.

CALayer *parentLayer = [CALayer layer];
CALayer *animtingLayer = [CALayer layer];

//FADE IN
        CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
        animation.beginTime = CMTimeGetSeconds(img.startTime);
        animation.duration = CMTimeGetSeconds(_timeline.transitionDuration);
        animation.fromValue = [NSNumber numberWithFloat:0.0f];
        animation.toValue = [NSNumber numberWithFloat:1.0f];
        animation.removedOnCompletion = NO;
        animation.fillMode = kCAFillModeBoth;
        animation.additive = NO;
        [parentLayer addAnimation:animation forKey:@"opacityIN"];

//FADE OUT
        CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
        animation.beginTime = CMTimeGetSeconds(CMTimeAdd(img.passTimeRange.start, img.passTimeRange.duration));
        animation.duration = CMTimeGetSeconds(_timeline.transitionDuration);
        animation.fromValue = [NSNumber numberWithFloat:1.0f];
        animation.toValue = [NSNumber numberWithFloat:0.0f];
        animation.removedOnCompletion = NO;
        animation.fillMode = kCAFillModeBoth;
        animation.additive = NO;
        [animtingLayer addAnimation:animation forKey:@"opacityOUT"];

    [parentLayer addSublayer:animtingLayer];
28
comonitos

Homme, tellement de réponses compliquées. Le moyen le plus simple est d’ajouter une fonction autoreverse. Voila.

CABasicAnimation *fadeInAndOut = [CABasicAnimation animationWithKeyPath:@"opacity"];
fadeInAndOut.duration = 5.0;
fadeInAndOut.autoreverses = YES;
fadeInAndOut.fromValue = [NSNumber numberWithFloat:0.0];
fadeInAndOut.toValue = [NSNumber numberWithFloat:1.0];
fadeInAndOut.repeatCount = HUGE_VALF;
fadeInAndOut.fillMode = kCAFillModeBoth;
[titleLayer addAnimation:fadeInAndOut forKey:@"myanimation"];
25
Mikhail Larionov

Cela fonctionne pour moi:

 let fadeAnimation = CAKeyframeAnimation(keyPath:"opacity")
 fadeAnimation.beginTime = AVCoreAnimationBeginTimeAtZero + start
 fadeAnimation.duration = duration
 fadeAnimation.keyTimes = [0, 1/8.0, 5/8.0, 1]
 fadeAnimation.values = [0.0, 1.0, 1.0, 0.0]
 fadeAnimation.removedOnCompletion = false
 fadeAnimation.fillMode = kCAFillModeForwards
 layer.addAnimation(fadeAnimation, forKey:"animateOpacity")
 layer.opacity = 0.0
2
LenK

Le point est le nom de la clé. Vous devez définir la clé d'opacité.

layer.add(animation, forKey: nil)         // Not Working
layer.add(animation, forKey: "opacity")   // Working

Vérifiez l'exemple de code. J'ai testé dans Swift 4

    let animation                   = CAKeyframeAnimation()
    animation.duration              = 1.53
    animation.autoreverses          = false
    animation.keyTimes              = [0, 0.51, 0.85, 1.0]
    animation.values                = [0.5, 0.5, 1.0, 0.5]
    animation.beginTime             = 0
    animation.isRemovedOnCompletion = false
    animation.fillMode              = kCAFillModeBoth
    animation.repeatCount           = .greatestFiniteMagnitude
    layer.add(animation, forKey: "opacity")
1
Den

La méthode alternative consiste à utiliser CAAnimationGroup. CAKeyframeAnimation fonctionne également très bien pour l'interface utilisateur. Ce code fonctionne bien dans l'interface utilisateur pour l'animation d'une émission en temps réel. Mais ne fonctionnera pas du tout dans AVVideoCompositionCoreAnimationTool - veuillez faire défiler vers le bas si vous en avez besoin. Ce n'est pas un code prêt à être copié-collé mais vous pouvez en avoir l'idée. En outre, vous pouvez ajouter des animations supplémentaires au groupe:

for (HKAnimatedLayer *animLayer in layersList) {

    overlayLayer = [CALayer layer];
    [overlayLayer setContents:(id)[animLayer.image CGImage]];
    overlayLayer.frame = CGRectMake(0, 0, size.width, size.height);
    [overlayLayer setMasksToBounds:YES];

    NSMutableArray *animations = [NSMutableArray array];
    // Step 1
    {
        CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
        animation.toValue = @(0);
        animation.duration = kLayerFadeDuration;
        animation.beginTime = kMovieDuration/5;
        animation.fillMode = kCAFillModeForwards;
        [animations addObject:animation];
    }
    {
        CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
        animation.toValue = @(1.0);
        animation.duration = kLayerFadeDuration;
        animation.beginTime = kMovieDuration*2/3;
        animation.fillMode = kCAFillModeForwards;
        [animations addObject:animation];
    }

    CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
    animationGroup.animations = animations;
    animationGroup.duration = kMovieDuration;
    animationGroup.fillMode = kCAFillModeForwards;
    animationGroup.removedOnCompletion = YES;

    [overlayLayer addAnimation:animationGroup forKey:nil];

    [parentLayer addSublayer:overlayLayer];
}

Voici une petite note sur l'animation sur les calques pour AVVideoCompositionCoreAnimationTool. Vous pouvez voir l’image gif correspondante (les titres doivent apparaître et disparaître un à un). Pour résoudre ce problème, j'utilise 2 variables CALayer car, pour une raison quelconque, sur un calque 2, des animations opaque sur plusieurs calques sont enchevêtrées.

 AVVideoCompositionCoreAnimationTool

// set up the parent layer
CALayer *parentLayer = [CALayer layer];
parentLayer.frame = CGRectMake(0, 0, size.width, size.height);

// one layer for one animation
CALayer *overlayLayer, *barrierLayer;
CABasicAnimation *animation;

for (HKAnimatedLayer *animLayer in layersList) {
    overlayLayer = [CALayer layer];
    overlayLayer.contents = (id)[animLayer.image CGImage];
    overlayLayer.frame = CGRectMake(0, 0, size.width, size.height);
    overlayLayer.masksToBounds = YES;

    // layer with appear animation
    if (animLayer.fromTime != 0 && (animLayer.fromTime - kLayerFadeDuration)>0) {
        overlayLayer.opacity = 0.0;

        animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
        animation.fromValue = @(0);
        animation.toValue = @(1);
        animation.additive = NO;
        animation.removedOnCompletion = NO;
        animation.beginTime = animLayer.fromTime - kLayerFadeDuration;
        animation.duration = kLayerFadeDuration;
        animation.fillMode = kCAFillModeForwards;

        [overlayLayer addAnimation:animation forKey:@"fadeIn"];
    }

    if (animLayer.toTime == kMovieDuration) {
        [parentLayer addSublayer:overlayLayer];
    } else { // layer with dissappear animation
        barrierLayer = [CALayer layer];
        barrierLayer.frame = CGRectMake(0, 0, size.width, size.height);
        barrierLayer.masksToBounds = YES;
        [barrierLayer addSublayer:overlayLayer];

        animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
        animation.fromValue = @(1);
        animation.toValue = @(0);
        animation.additive = NO;
        animation.removedOnCompletion = NO;
        animation.beginTime = animLayer.toTime;
        animation.duration = kLayerFadeDuration;
        animation.fillMode = kCAFillModeForwards;

        [overlayLayer addAnimation:animation forKey:@"fadeOut"];
        [parentLayer addSublayer:barrierLayer];
    }
}

Et à la fin, nous pouvons obtenir une séquence d'animation appropriée  right animation

1
WINSergey

Essayer:

fadeInAnimation.beginTime = CACurrentMediaTime()+1.0;
1
Peter DeWeese

Je pense que vous cherchez timeOffset, pas beginTime...

1
nielsbot

Le cœur du problème ne comprenait pas la propriété keyTimes d'un fichier CAKeyframeAnimation. Cette question a tout clarifié et m'a mis dans la bonne voie:

Quel type de valeur est keyTime dans un CAKeyFrameAnimation?

1
gamblor87

Emprunter du lien mentionné par gamblor87 et ajouter mes commentaires comme explication.

//create a fadeInOut CAKeyframeAnimation on opacticy
CAKeyframeAnimation *fadeInAndOut = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];

//set duration
fadeInAndOut.duration = 5.0;

//autoreverses defaults to NO so we don't need this.
//fadeInAndOut.autoreverses = NO;

//keyTimes are time points on duration timeline as a fraction of animation duration (here 5 seconds).
fadeInAndOut.keyTimes = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0.0],
                                                  [NSNumber numberWithFloat:0.20],
                                                  [NSNumber numberWithFloat:0.80], 
                                                  [NSNumber numberWithFloat:1.0], nil];


//set opacity values at various points during the 5second animation
fadeInAndOut.values = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0.0],//opacity 0 at 0s (corresponds to keyTime = 0s/5s)
                                                [NSNumber numberWithFloat:1.0],//opacity 1 at 1s (corresponds to keyTime = 1s/5s)
                                                [NSNumber numberWithFloat:1.0],//opacity 1 upto 4s (corresponds to keyTime = 4s/5s)
                                                [NSNumber numberWithFloat:0.0],//opacity 0 at 5s (corresponds to keyTime = 5s/5s)
 nil];

//delay in start of animation. What we are essentially saying is to start the 5second animation after 1second.
fadeInAndOut.beginTime = 1.0;

//don't remove the animation on completion.
fadeInAndOut.removedOnCompletion = NO;

//fill mode. In most cases we won't need this.
fadeInAndOut.fillMode = kCAFillModeBoth;

//add the animation to layer
[titleLayer addAnimation:fadeInAndOut forKey:nil];
1
SayeedHussain

LenK la réponse a parfaitement fonctionné pour moi. Si quelqu'un est intéressé, j'ai ré-écrit pour Obj-C (note j'ai aussi légèrement changé les images clés en fondu en entrée):

CAKeyframeAnimation *fadeInAndOutAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
fadeInAndOutAnimation.beginTime = CACurrentMediaTime() + beginTime;
fadeInAndOutAnimation.duration = duration;
fadeInAndOutAnimation.keyTimes = @[@0.0, @( 2.0 / 8.0 ), @( 5.0 / 8.0 ), @1.0];
fadeInAndOutAnimation.values = @[@0.0, @1.0, @1.0, @0.0];
fadeInAndOutAnimation.removedOnCompletion = false;
fadeInAndOutAnimation.fillMode = kCAFillModeForwards;
1
Ryan

Regardez ma réponse https://stackoverflow.com/a/44204846/667483

En résumé: pour utiliser beginTime, vous devez définir fillMode sur kCAFillModeBackwards sur votre objet d'animation.

0
surfrider