Ответ 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
, она просто показывает, как она может работать в принципе.