Как анимация UIView с блоками работает под капотом - программирование
Подтвердить что ты не робот

Как анимация UIView с блоками работает под капотом

Я новичок в программировании Objective-C/iOS, и я пытаюсь понять, как работает анимация UIView под капотом.

Скажем, у меня есть такой код:

[UIView animateWithDuration:2.0 animations:^{
    self.label.alpha = 1.0;
}];

Вещей, которая передается как аргумент animations, является блок Objective-C (что-то вроде lambdas/анонимных функций на других языках), который может быть выполнен, а затем он изменяет свойство alpha label из текущего значение 1.0.

Однако блок не принимает аргумент прогресса анимации (скажем, от 0.0 до 1.0 или от 0 до 1000). Мой вопрос заключается в том, как анимационная среда использует этот блок, чтобы знать о промежуточных кадрах, поскольку блок определяет только конечное состояние.

EDIT: Мои вопросы скорее касаются работы с капюшоном animateWithDuration, а не способов ее использования.

Моя гипотеза о том, как работает код animateWithDuration, выглядит следующим образом:

  • animateWithDuration активирует какое-то особое состояние для всех объектов вида, в которых изменения фактически не выполняются, а только регистрируются.
  • он выполняет блок и изменения регистрируются.
  • он запрашивает объекты views для измененного состояния и возвращает исходные и целевые значения и, следовательно, знает, какие свойства нужно изменить и в каком диапазоне выполнить изменения.
  • он вычисляет промежуточные кадры на основе продолжительности и начальных/целевых значений и запускает анимацию.

Может ли кто-нибудь пролить свет на то, работает ли animateWithDuration таким образом?

4b9b3361

Ответ 1

Конечно, я не знаю, что именно происходит под капотом, потому что UIKit не является открытым исходным кодом, и я не работаю в Apple, но вот несколько идей:

До того, как были внедрены методы анимации на основе блоков UIView, анимационные представления выглядели так, и эти методы на самом деле все еще доступны:

[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:duration];
myView.center = CGPointMake(300, 300);
[UIView commitAnimations];

Зная это, мы можем реализовать собственный метод анимации на основе блоков, например:

+ (void)my_animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations
{
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:duration];
    animations();
    [UIView commitAnimations];
}

..., который будет делать то же, что и существующий метод animateWithDuration:animations:.

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

Фактическая анимация выполняется с помощью Core Animation, которая работает на уровне уровня - каждый UIView имеет резервный экземпляр CALayer, который отвечает за анимацию и композицию, в то время как представление в основном просто обрабатывает события касания и координирует преобразования системы.

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

Поскольку UIView основан на CALayer, многие его свойства фактически реализованы в нижележащем слое. Например, когда вы устанавливаете или получаете view.center, это то же самое, что и view.layer.location, и изменение любого из них также изменит другое.

Слои могут быть явно анимированы с помощью CAAnimation (который является абстрактным классом, который имеет ряд конкретных реализаций, например CABasicAnimation для простых вещей и CAKeyframeAnimation для более сложных вещей).

Итак, что может сделать установщик свойств UIView, чтобы выполнить "волшебные" анимационные изменения в блоке анимации? Давайте посмотрим, можем ли мы повторно реализовать один из них, для простоты, используйте setCenter:.

Во-первых, здесь приведена модифицированная версия метода my_animateWithDuration:animations: сверху, которая использует глобальный CATransaction, так что мы можем узнать в нашем методе setCenter:, как долго должна выполняться анимация:

- (void)my_animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations
{
    [CATransaction begin];
    [CATransaction setAnimationDuration:duration];

    animations();

    [CATransaction commit];
}

Обратите внимание, что мы больше не используем beginAnimations:... и commitAnimations, поэтому без каких-либо действий ничего не будет анимировано.

Теперь переопределите setCenter: в подкласс UIView:

@interface MyView : UIView
@end

@implementation MyView
- (void)setCenter:(CGPoint)position
{
    if ([CATransaction animationDuration] > 0) {
        CALayer *layer = self.layer;
        CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
        animation.fromValue = [layer valueForKey:@"position"];
        animation.toValue = [NSValue valueWithCGPoint:position];
        layer.position = position;
        [layer addAnimation:animation forKey:@"position"];
    }
}
@end

Здесь мы настраиваем явную анимацию с использованием Core Animation, которая анимирует свойство нижележащего слоя location. Длительность анимации будет автоматически взята из CATransaction. Попробуйте:

MyView *myView = [[MyView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
myView.backgroundColor = [UIColor redColor];
[self.view addSubview:myView];

[self my_animateWithDuration:4.0 animations:^{
    NSLog(@"center before: %@", NSStringFromCGPoint(myView.center));
    myView.center = CGPointMake(300, 300);
    NSLog(@"center after : %@", NSStringFromCGPoint(myView.center));
}];

Я не говорю, что именно так работает анимационная система UIView, она просто показывает, как она может работать в принципе.

Ответ 2

Значения промежуточных кадров для не указаны; анимация значений (альфа в этом случае, а также цвета, положение и т.д.) генерируется автоматически между ранее установленным значением и значением назначения, установленным внутри блока анимации. Вы можете повлиять на кривую, указав параметры с помощью animateWithDuration:delay:options:animations:completion: (по умолчанию UIViewAnimationOptionCurveEaseInOut, то есть скорость изменения значения будет ускоряться и замедляться).

Обратите внимание, что все ранее установленные анимированные изменения значений заканчиваются первым, т.е. каждый блок анимации указывает новую анимацию от предыдущего конечного значения к новому. Вы можете указать UIViewAnimationOptionBeginFromCurrentState, чтобы запустить новую анимацию из состояния уже выполненной анимации.

Ответ 3

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

Итак, если исходное значение альфа-значения равно 0, это будет исчезать на этикетке в течение 2 секунд. Если исходные значения были уже 1.0, вы вообще не увидели бы никакого эффекта.

Под капотом UIView позаботится о том, сколько кадров анимации должно произойти.

Вы также можете изменить скорость, с которой происходит изменение, указав кривую ослабления как UIViewAnimationOption. Опять же, UIView обрабатывает анимацию для вас.