6

在关键帧动画的另一项测试中,我将theImage沿贝塞尔路径移动 UIImageView(称为 我的初始代码中包含这些元素来启动动画:

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. 最终以缩小后的尺寸绘制图像。

所以这似乎是我看到的第 4 步闪烁。


编辑 3 月 22 日

我刚刚向 GitHub 上传了一个演示项目,该项目展示了对象沿贝塞尔路径的移动。代码可以在PathMove找到

我还在我的博客“在 iOS 中沿贝塞尔路径移动对象”中写到了它

4

3 回答 3

8

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

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

  • 更改图层的属性通常会向图层添加一个隐式动画,其中属性名称作为动画的键。所以如果你想显式地为一个属性设置动画,你通常希望将属性设置为它的最终值,然后添加一个以属性名称为键的动画,以覆盖隐式动画。

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

此外,令人困惑的是,您为视图的边界设置动画以将其放大,然后在最后切换到缩放转换。

我认为最简单的方法是UIView尽可能多地使用动画方法,并且只为关键帧动画引入核心动画。您可以在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];
    }];
}
于 2012-03-03T20:33:50.127 回答
4

这周我正在尝试一些 CAAnimations,并注意到我的动画结束时有一个闪烁。特别是,我会将动画从圆形变为方形,同时更改填充颜色。

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

而且,我构建了这个动画,它对我来说很好,最后没有故障:设置和触发器是我在窗口中的自定义方法,我在鼠标按下时触发动画。

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

    }];
}
于 2012-03-03T07:27:36.777 回答
4

CAAnimation如果终端状态的分配方式使其本身创建了一个隐式动画,则 s 将在最后闪烁。请记住,CAAnimations 是为了可视化过渡而对对象属性进行的临时调整。动画完成后,如果图层的状态仍然是原始的起始状态,那么在您设置最终图层状态之前,这将是暂时显示的状态,您可以在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:方法中删除变换调整。

于 2012-03-03T16:45:43.180 回答