动画CAShapeLayer大小变化

18

我正在画一个圆,初始半径为200。

self.circle = [CAShapeLayer layer];
self.circle.fillColor = nil;
self.circle.strokeColor = [UIColor blackColor].CGColor;
self.circle.lineWidth = 7;
self.circle.bounds = CGRectMake(0, 0, 2 * radius, 2 * radius);
self.circle.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.radius, self.radius)

请问有人可以告诉我如何将半径动画更改为100吗?


如果您在动画块中修改边界,它不会执行动画吗? - Lefteris
@Jonathan,我的回答不起作用吗? - Jacob Relkin
不幸的是,不行。我可能做错了,但圆圈只会重新绘制。 - Jonathan
4个回答

23

这就是我最终如何做到的:

UIBezierPath *newPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(newRadius, newRadius) radius:newRadius startAngle:(-M_PI/2) endAngle:(3*M_PI/2) clockwise:YES];
CGRect newBounds = CGRectMake(0, 0, 2 * newRadius, 2 * newRadius);

CABasicAnimation* pathAnim = [CABasicAnimation animationWithKeyPath: @"path"];
pathAnim.toValue = (id)newPath.CGPath;

CABasicAnimation* boundsAnim = [CABasicAnimation animationWithKeyPath: @"bounds"];
boundsAnim.toValue = [NSValue valueWithCGRect:newBounds];

CAAnimationGroup *anims = [CAAnimationGroup animation];
anims.animations = [NSArray arrayWithObjects:pathAnim, boundsAnim, nil];
anims.removedOnCompletion = NO;
anims.duration = 2.0f;
anims.fillMode  = kCAFillModeForwards;

[self.circle addAnimation:anims forKey:nil];

你不应该使用removedOnCompletion和fillMode。你应该这样做:self.circle = newPath.CGPath; [self.circle addAnimation:...]。这样,当动画结束时,路径将是正确的,并且你不会泄漏动画对象。 - nevyn
我以前用过这种方法,有时会导致内存泄漏。你正在使动画发生,但并没有以永久的方式改变self.circle.path的基础值...你只是留下了一个未删除的动画附加在上面,使路径看起来像是被永久地改变了。请参见我的其他答案,其中提供了一种更清洁的方法(并遵循了苹果的文档)。我为此苦苦挣扎了很长时间,但现在终于明白它是如何工作的了! - Chadwick Wood

13

我认为这是更简单和更优选的方法:

UIBezierPath *newPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(newRadius, newRadius) radius:newRadius startAngle:(-M_PI/2) endAngle:(3*M_PI/2) clockwise:YES];
CABasicAnimation* pathAnim = [CABasicAnimation animationWithKeyPath:@"path"];
pathAnim.fromValue = (id)self.circle.path;
pathAnim.toValue = (id)newPath.CGPath;
pathAnim.duration = 2.0f;
[self.circle addAnimation:pathAnim forKey:@"animateRadius"];

self.circle.path = newPath.CGPath;

我从这里的Apple文档(见清单3-2)中获得了这种方法。重要的是,您正在设置fromValue,并在设置动画后立即将self.circlepath的最终值设置为newPath.CGPath。动画不会更改模型的基础值,因此在另一种方法中,您实际上没有对形状图层的path变量进行永久更改。
我以前使用过类似于所选答案的解决方案(使用removedOnCompletionfillMode等),但我发现在某些iOS版本上它们会导致内存泄漏,并且有时该方法出现问题。
使用这种方法,您显式创建动画(包括其起始和结束位置),并在最后指定路径的最终固定值(通过最后一行),因此在完成时无需操纵不删除动画(如果您多次执行此动画会导致内存泄漏...未删除的动画会在内存中积累)。
此外,我删除了bounds的动画,因为您不需要它来对圆形进行动画。即使路径绘制在图层的边界之外,它仍将完全显示(有关此内容,请参阅CAShapeLayer文档)。如果您确实需要出于其他原因对范围进行动画处理,则可以使用相同的方法(确保指定fromValue和范围的最终值)。

这个动画在我尝试时会导致扭曲。这里是动画进行中的一些截图。(从小到大生长)。结束后就恢复正常了。 - Micrified

2

您可以使用CAAnimationGroup来动画化CAShapeLayerboundspath

- (void)animateToRadius:(CGFloat)newRadius {
   CAShapeLayer *shapeLayer = ...;

   UIBezierPath *newBezierPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(newRadius, newRadius)];
   NSValue *newBounds = [NSValue valueWithCGRect:CGRectMake(0,0,2*newRadius,2*newRadius);

   CAAnimationGroup *group = [CAAnimationGroup animation];

   NSMutableArray *animations  = [@[] mutableCopy];
   NSDictionary *animationData = @{@"path":newBezierPath.CGPath, @"bounds":newBounds};
   for(NSString *keyPath in animationData) {
      CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:keyPath];
      animation.toValue   = animationData[keyPath];
      [animations addObject:animation];
   }

   group.animations = [animations copy];
   group.duration = 2;
   group.removedOnCompletion = NO;
   group.fillMode = kCAFillModeForwards;

   [shapeLayer addAnimation:group forKey:nil];
}

谢谢Jacob,但不幸的是这并不起作用。圆圈保持相同大小,只是移动位置。 - Jonathan

2

如果需要,这是Jonathan代码的Swift直接转换。

func scaleShape(shape : CAShapeLayer){

    let newRadius : CGFloat = 50;

    let newPath: UIBezierPath = UIBezierPath(arcCenter: CGPointMake(newRadius, newRadius), radius: newRadius, startAngle: (CGFloat(-M_PI) / 2.0), endAngle: (3 * CGFloat(M_PI) / 2), clockwise: true);


    let newBounds : CGRect = CGRect(x: 0, y: 0, width: 2 * newRadius, height: 2 * newRadius);
    let pathAnim : CABasicAnimation = CABasicAnimation(keyPath: "path");

    pathAnim.toValue = newPath.CGPath;

    let boundsAnim : CABasicAnimation = CABasicAnimation(keyPath: "bounds");
    boundsAnim.toValue = NSValue(CGRect: newBounds);

    let anims: CAAnimationGroup = CAAnimationGroup();
    anims.animations = [pathAnim, boundsAnim];
    anims.removedOnCompletion = false;
    anims.duration = 2.0;
    anims.fillMode = kCAFillModeForwards;

    shape.addAnimation(anims, forKey: nil);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接