Подтвердить что ты не робот

Анимация изменения размера CAShapeLayer

Я рисую круг с начальным радиусом 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?

4b9b3361

Ответ 1

Вот как я это сделал:

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

Спасибо Джейкобу за то, что он указал мне в правильном направлении.

Ответ 2

Это, я считаю, более простой и предпочтительный способ сделать это:

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 Documentation здесь (см. список 3-2). Важно то, что вы устанавливаете fromValue, а также устанавливаете окончательное значение для self.circle path на newPath.CGPath сразу после настройки анимации. Анимация не изменяет базовое значение модели, поэтому в другом подходе вы фактически не вносите постоянного изменения в переменную типа path.

Я использовал решение, подобное выбранному в прошлом (используя removedOnCompletion и fillMode и т.д.), но я обнаружил, что в некоторых версиях iOS они вызвали утечку памяти, а также иногда этот подход просто не работа по какой-то причине.

При таком подходе вы создаете анимацию явно (как там, где она начинается, так и заканчивается), и вы указываете окончательное постоянное значение для пути в конце (с этой последней строкой), так что нет необходимости возиться с не удалением анимации, когда она заканчивается (что, я считаю, приводит к утечке памяти, если вы много раз делаете эту анимацию... неуправляемые анимации накапливаются в памяти).

Кроме того, я удалил анимацию bounds, потому что она не нужна для анимации круга. Даже если путь выведен за пределы слоя, он все равно будет полностью показан (см. Документацию для CAShapeLayer об этом). Если вам нужно анимировать границы по какой-либо другой причине, вы можете использовать один и тот же подход (обязательно указывая fromValue и конечное значение для границ).

Ответ 3

Вы можете использовать CAAnimationGroup для анимации bounds и path вашего CAShapeLayer:

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

Ответ 4

Вот прямое быстрое преобразование кода Джонатана, если кому-то это понадобится.

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