iOS CAKeyFrameAnimation Scaling Flickers at animation end

后端 未结 3 2176
刺人心
刺人心 2021-02-06 12:29

In another test of Key Frame animation I am combining moving a UIImageView (called theImage) along a bezier path and scaling larger it as it moves, resulting in a 2

相关标签:
3条回答
  • 2021-02-06 12:58

    It can be tricky to animate a view's layer using Core Animation. There are several things that make it confusing:

    • Setting an animation on a layer doesn't change the layer's properties. Instead, it changes the properties of a “presentation layer” that replaces the original “model layer” on the screen as long as the animation is applied.

    • Changing a layer's property normally adds an implicit animation to the layer, with the property name as the animation's key. So if you want to explicitly animate a property, you usually want to set the property to its final value, then add an animation whose key is the property name, to override the implicit animation.

    • A view normally disables implicit animations on its layer. It also mucks around with its layer's properties in other somewhat mysterious ways.

    Also, it's confusing that you animate the view's bounds to scale it up, but then switch to a scale transformation at the end.

    I think the easiest way to do what you want is to use the UIView animation methods as much as possible, and only bring in Core Animation for the keyframe animation. You can add the keyframe animation to the view's layer after you've let UIView add its own animation, and your keyframe animation will override the animation added by UIView.

    This worked for me:

    - (IBAction)animate:(id)sender {
        UIImageView* theImage = self.imageView;
        CGFloat scaleFactor = 2;
        NSTimeInterval duration = 1;
    
        UIBezierPath *path = [self animationPathFromStartingPoint:theImage.center];
        CGPoint destination = [path currentPoint];
    
        [UIView animateWithDuration:duration animations:^{
            // UIView will add animations for both of these changes.
            theImage.transform = CGAffineTransformMakeScale(scaleFactor, scaleFactor);
            theImage.center = destination;
    
            // Prepare my own keypath animation for the layer position.
            // The layer position is the same as the view center.
            CAKeyframeAnimation *positionAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
            positionAnimation.path = path.CGPath;
    
            // Copy properties from UIView's animation.
            CAAnimation *autoAnimation = [theImage.layer animationForKey:@"position"];
            positionAnimation.duration = autoAnimation.duration;
            positionAnimation.fillMode = autoAnimation.fillMode;
    
            // Replace UIView's animation with my animation.
            [theImage.layer addAnimation:positionAnimation forKey:positionAnimation.keyPath];
        }];
    }
    
    0 讨论(0)
  • 2021-02-06 13:12

    I was experimenting with some CAAnimations this week and was noticing that there was a flickering at the end of my animations. In particular, I would animation from a circle to a square, while changing the fillColor as well.

    Each CAAnimation has a property called removedOnCompletion which defaults to YES. This means that the animation will disappear (i.e. transitions, scales, rotations, etc.) when the animation completes and you'll be left with the original layer.

    Since you already have set your removedOnCompletion properties to NO, I would suggest trying to shift your execution of your animations to use CATransactions, instead of delegates and animationDidStop...

    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    [CATransaction setCompletionBlock: ^{ theImage.transform = ...}];
    
    // ... CAAnimation Stuff ... //
    
    [CATransaction commit];
    

    You put the transaction's completion block call before you create your animations, as per: http://zearfoss.wordpress.com/2011/02/24/core-animation-catransaction-protip/

    The following is from one of my methods:

    [CATransaction begin];
    CABasicAnimation *animation = ...;
    animation.fromValue = ...;
    animation.toValue = ...;
    [CATransaction setCompletionBlock:^ { self.shadowRadius = _shadowRadius; }];
    [self addAnimation:animation forKey:@"animateShadowOpacity"];
    [CATransaction commit];
    

    And, I constructed this animation and it works fine for me with no glitches at the end: The setup and trigger are custom methods I have in a window, and i trigger the animation on mousedown.

    UIImageView *imgView;
    UIBezierPath *animationPath;
    
    -(void)setup {
        canvas = (C4View *)self.view;
        imgView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"img256.png"]];
        imgView.frame = CGRectMake(0, 0, 128, 128);
        imgView.center = CGPointMake(384, 128);
        [canvas addSubview:imgView];
    
    }
    
    -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
        [UIImageView animateWithDuration:2.0f animations:^{
            [CATransaction begin];        
            CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
            pathAnimation.duration = 2.0f;
            pathAnimation.calculationMode = kCAAnimationPaced;
            animationPath = [UIBezierPath bezierPath];
            [animationPath moveToPoint:imgView.center];
            [animationPath addLineToPoint:CGPointMake(128, 512)];
            [animationPath addLineToPoint:CGPointMake(384, 896)];
            pathAnimation.path = animationPath.CGPath;
            pathAnimation.fillMode = kCAFillModeForwards;
            pathAnimation.removedOnCompletion = NO;
            [imgView.layer addAnimation:pathAnimation forKey:@"animatePosition"];
            [CATransaction commit];
    
            CGFloat scaleFactor = 2.0f;
            CGRect newFrame = imgView.frame;
            newFrame.size.width *= scaleFactor;
            newFrame.size.height *= scaleFactor;
            newFrame.origin = CGPointMake(256, 0);
            imgView.frame = newFrame;
            imgView.transform = CGAffineTransformRotate(imgView.transform,90.0*M_PI/180);
    
        }];
    }
    
    0 讨论(0)
  • 2021-02-06 13:13

    CAAnimations will flicker at the end if the terminal state was assigned in such a way that it itself created an implicit animation. Keep in mind CAAnimations are temporary adjustments of an object properties for the purposes of visualizing transition. When the animation done, if the layer's state is still the original starting state, that is what is going to be displayed ever so temporarily until you set the final layer state, which you do in your animationDidStop: method.

    Furthermore, your animation is adjusting the bounds.size property of your layer, so you should similarly set your final state rather than using the transform adjustment as your final state. You could also use the transform property as the animating property in the animation instead of bounds.size.

    To remedy this, immediately after assigning the animation, change the layer's permeant state to your desired terminal state so that when the animation completes there will be no flicker, but do so in such a manner to no trigger an implicit animation before the animation begins. Specifically, in your case you should do this at the end of your animation set up:

    UIImageView* theImage = ....
    float scaleFactor = 2.0;
    ....
    
    theImage.center = destination;
    theImage.transform = CGAffineTransformMakeScale(1.0,1.0);
    
    CGSize finalSize = CGSizeMake(theImage.image.size.height*scaleFactor, theImage.image.size.width*scaleFactor);
    CABasicAnimation *resizeAnimation = [CABasicAnimation animationWithKeyPath:@"bounds.size"];
    [resizeAnimation setToValue:[NSValue valueWithCGSize:finalSize]];
    resizeAnimation.fillMode = kCAFillModeBackwards;
    resizeAnimation.removedOnCompletion = NO;  
    
    CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    pathAnimation.path = [jdPath path].CGPath;
    pathAnimation.fillMode = kCAFillModeBackwards;
    pathAnimation.removedOnCompletion = NO;
    
    CAAnimationGroup* group = [CAAnimationGroup animation];
    group.animations = [NSArray arrayWithObjects:pathAnimation, resizeAnimation, nil];
    group.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    group.removedOnCompletion = NO;
    group.duration = duration;
    group.delegate = self;
    
    [theImage.layer addAnimation:group forKey:@"animateImage"];
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    theImage.bounds = CGRectMake( theImage.bounds.origin.x, theImage.bounds.origin.y, finalSize.width, finalSize.height );
    [CATransaction commit];
    

    and then remove the transform adjustment in your animationDidStop: method.

    0 讨论(0)
提交回复
热议问题