iOS CAKeyFrameAnimation Scaling在动画结束时闪烁

时间:2012-02-29 14:33:21

标签: ios uiimageview scaling flicker cakeyframeanimation

在关键帧动画的另一个测试中,我正在组合沿着贝塞尔曲线路径移动UIImageView(称为theImage)并在移动时缩放它,导致路径末端的图像大2倍。我这样做的初始代码中包含了这些元素来启动动画:

UIImageView* theImage = ....
float scaleFactor = 2.0;
....

theImage.center = destination;
theImage.transform = CGAffineTransformMakeScale(1.0,1.0);

CABasicAnimation *resizeAnimation = [CABasicAnimation animationWithKeyPath:@"bounds.size"];
[resizeAnimation setToValue:[NSValue valueWithCGSize:CGSizeMake(theImage.image.size.height*scaleFactor, theImage.image.size.width*scaleFactor)]];
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"];

然后,当动画完成时,我想保留更大尺寸的图像,所以我实现了:

- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
   theImage.transform = CGAffineTransformMakeScale(scaleFactor,scaleFactor);
}

这一切都有效..有点儿。问题是在动画结束时theImage闪烁了一会儿 - 足以使它看起来很糟糕。我猜这是动画结束时的过渡,我将变换设置为新大小。

在试验这个时,我尝试了一种略微不同的上述形式,但仍然有相同的闪烁:

CAKeyframeAnimation *resizeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
NSValue* startSizeKey = [NSValue valueWithCATransform3D:CATransform3DScale (theImage.layer.transform, 1.0, 1.0, 1.0)];
NSValue* endSizeKey = [NSValue valueWithCATransform3D:CATransform3DScale (theImage.layer.transform, scaleFactor, scaleFactor, 1.0)]; 
NSArray* sizeKeys = [NSArray arrayWithObjects:startSizeKey, endSizeKey, nil];
[resizeAnimation setValues:sizeKeys];

....

theImage.transform = CGAffineTransformMakeScale(scaleFactor,scaleFactor);

但是当我以与原版相同的大小结束动画时,没有闪烁:

CAKeyframeAnimation *resizeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
NSValue* startSizeKey = [NSValue valueWithCATransform3D:CATransform3DScale (theImage.layer.transform, 1.0, 1.0, 1.0)];
NSValue* middleSizeKey = [NSValue valueWithCATransform3D:CATransform3DScale (theImage.layer.transform, scaleFactor, scaleFactor, 1.0)];
NSValue* endSizeKey = [NSValue valueWithCATransform3D:CATransform3DScale (theImage.layer.transform, 1.0, 1.0, 1.0)]; 
NSArray* sizeKeys = [NSArray arrayWithObjects:startSizeKey, middleSizeKey, endSizeKey, nil];
[resizeAnimation setValues:sizeKeys];

....

theImage.transform = CGAffineTransformMakeScale(1.0,1.0);

所以我的大问题是如何在没有闪烁的情况下为此图像设置动画,并在动画结束时以不同的尺寸结束?


编辑3月2日

我的初步测试是将图像缩放。我只是尝试缩小它(IE scaleFactor = 0.4)并且闪烁更加明显,而且我所看到的更加明显。这是一系列事件:

  1. 原始尺寸的图像在起始位置的屏幕上绘制。
  2. 当图像沿路径移动时,它会平滑收缩。
  3. 完全收缩的图像到达路径的末尾。
  4. 然后以原始尺寸绘制图像。
  5. 图像最终以缩小的尺寸绘制。
  6. 所以似乎步骤4就是我所看到的闪烁。


    编辑3月22日

    我刚刚向GitHub上传了一个演示项目,该项目展示了沿着贝塞尔路径移动物体的过程。代码可以在PathMove

    找到

    我也在Moving objects along a bezier path in iOS

    的博客中写过这篇文章

3 个答案:

答案 0 :(得分:7)

使用Core Animation为视图的图层设置动画可能很棘手。有几件事令人困惑:

  • 在图层上设置动画不会更改图层的属性。相反,它会更改“表示层”的属性,只要应用动画,它就会替换屏幕上的原始“模型层”。

  • 更改图层的属性通常会向图层添加隐式动画,并将属性名称作为动画的键。因此,如果要显式动画属性,通常需要将属性设置为其最终值,然后添加其键为属性名称的动画,以覆盖隐式动画。

  • 视图通常会在其图层上禁用隐式动画。它还以其他有些神秘的方式在其图层的属性上进行修复。

另外,让您为视图的界限设置动画以进行缩放比较令人困惑,但最后切换到比例变换。

我认为最简单的方法就是尽可能地使用UIView动画方法,并且只为关键帧动画引入Core Animation。在让UIView添加自己的动画后,您可以将关键帧动画添加到视图的图层,并且您的关键帧动画将覆盖由UIView添加的动画。

这对我有用:

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

答案 1 :(得分:4)

本周我正在尝试一些CAAnimations,并注意到我的动画结束时出现了闪烁。特别是,我会从圆圈到正方形进行动画制作,同时也可以更改fillColor。

每个CAAnimation都有一个名为removedOnCompletion的属性,默认为YES。这意味着当动画完成时动画将消失(即过渡,缩放,旋转等),并且您将留下原始图层。

由于您已将removedOnCompletion属性设置为NO,我建议您尝试将动画的执行转移到使用CATransactions,而不是委托和animationDidStop ......

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

// ... CAAnimation Stuff ... //

[CATransaction commit];

在创建动画之前,您将完成事务的完成块调用,具体如下: http://zearfoss.wordpress.com/2011/02/24/core-animation-catransaction-protip/

以下是我的一种方法:

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

并且,我构建了这个动画,它对我来说很好,最后没有毛刺: 设置和触发器是我在窗口中的自定义方法,我在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);

    }];
}

答案 2 :(得分:3)

如果终端状态以其自身创建隐式动画的方式分配,则

CAAnimation将在末尾闪烁。请记住CAAnimation是对象属性的临时调整,以便可视化过渡。完成动画后,如果图层的状态仍然是原始的开始状态,那么在您设置最终图层状态之前,这将是暂时显示的,这是您在animationDidStop:方法中所做的。

此外,您的动画正在调整图层的bounds.size属性,因此您应该类似地设置最终状态,而不是将变换调整用作最终状态。您还可以使用transform属性作为动画中的动画属性,而不是bounds.size

要解决此问题,请在分配动画后立即将图层的渗透状态更改为所需的终端状态,以便在动画完成时不会出现闪烁,但这样做是为了不会触发隐式动画。动画开始了。具体来说,在您的情况下,您应该在动画设置结束时执行此操作:

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

然后删除animationDidStop:方法中的转换调整。