Свойства CAGradientLayer не анимируются в анимационном блоке UIView - PullRequest
13 голосов
/ 21 февраля 2012

У меня такое чувство, что я упускаю что-то элементарное, но что может быть лучше, чем ошибиться в Интернете?

У меня довольно простой пользовательский интерфейс. Представление для моего UIViewController является подклассом, чей +layerClass равен CAGradientLayer. В зависимости от действий пользователя, мне нужно переместить некоторые элементы пользовательского интерфейса и изменить значения градиента фона. Код выглядит примерно так:

[UIView animateWithDuration:0.3 animations:^{
  self.subview1.frame = CGRectMake(...);
  self.subview2.frame = CGRectMake(...);
  self.subview2.alpha = 0;

  NSArray* newColors = [NSArray arrayWithObjects:
                         (id)firstColor.CGColor,
                         (id)secondColor.CGColor,
                         nil];
  [(CAGradientLayer *)self.layer setColors:newColors];
}];

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

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

У меня есть , чтобы использовать явный CAAnimation для этой работы? (И если да, то почему?)

Ответы [ 2 ]

13 голосов
/ 28 февраля 2012

Кажется, здесь происходят две вещи. Первая (как правильно указывает Трэвис и в документации говорится) заключается в том, что анимации UIKit, похоже, не влияют на неявную анимацию, применяемую к изменениям CALayer свойств. Я думаю, что это странно (UIKit должен использовать Core Animation), но это то, что есть.

Вот (возможно, очень глупый?) Способ решения этой проблемы:

  NSTimeInterval duration = 2.0; // slow things down for ease of debugging
  [UIView animateWithDuration:duration animations:^{
    [CATransaction begin];
    [CATransaction setAnimationDuration:duration];

    // ... do stuff to things here ...

    [CATransaction commit];
  }];

Другой ключ в том, что этот градиентный слой является слоем моего вида. Это означает, что мое представление является делегатом слоя (где, если бы градиентный слой был просто подслоем, у него не было бы делегата). И реализация UIView -actionForLayer:forKey: возвращает NSNull для события "colors". (Вероятно, каждое событие, которого нет в определенном списке анимаций UIView.)

Добавление следующего кода в мое представление приведет к тому, что изменение цвета будет анимированным, как и ожидалось:

- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event
{
  id<CAAction> action = [super actionForLayer:layer forKey:event];
  if( [@"colors" isEqualToString:event]
      && (nil == action || (id)[NSNull null] == action) ) {
    action = [CABasicAnimation animationWithKeyPath:event];
  }
  return action;
}
1 голос
/ 25 февраля 2012

Вы должны использовать явные CAAnimations, потому что вы меняете значение CALayer. UIViewAnimations работают со свойствами UIView, но не напрямую со своими свойствами CALayer ...

На самом деле вы должны использовать CABasicAnimation, чтобы иметь доступ к его свойствам fromValue и toValue.

Следующий код должен работать для вас:

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [UIView animateWithDuration:2.0f
                          delay:0.0f
                        options:UIViewAnimationCurveEaseInOut
                     animations:^{
                         CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"colors"];
                         animation.duration = 2.0f;
                         animation.delegate = self;
                         animation.fromValue = ((CAGradientLayer *)self.layer).colors;
                         animation.toValue = [NSArray arrayWithObjects:(id)[UIColor blackColor].CGColor,(id)[UIColor whiteColor].CGColor,nil];
                         [self.layer addAnimation:animation forKey:@"animateColors"];
                     }
                     completion:nil];
}

-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    NSString *keyPath = ((CAPropertyAnimation *)anim).keyPath;
    if ([keyPath isEqualToString:@"colors"]) {
        ((CAGradientLayer *)self.layer).colors = ((CABasicAnimation *)anim).toValue;
    }
}

У CAAnimations есть хитрость в том, что вам НЕОБХОДИМО явно установить значение свойства ПОСЛЕ того, как вы завершите анимацию.

Вы делаете это, устанавливая делегат, в этом случае я устанавливаю его для объекта, который вызывает анимацию, а затем перезаписываю его метод animationDidStop:finished:, чтобы включить настройку цветов CAGradientLayer к их окончательному значению.

Вам также придется выполнить небольшое приведение в методе animationDidStop:, чтобы получить доступ к свойствам анимации.

...