当然,我并不知道 UIKit 在底层做了什么,因为它不是开源的,而且我也不在苹果工作。但是以下是我的一些想法:
在引入基于块的 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
使用它来在动画块内完成对其(可动画)属性的更改。这必须是某种堆栈,因为您可以有嵌套的动画块。
实际的动画由核心动画执行,它在层级别上工作-每个UIView
都有一个后备CALayer
实例,负责动画和合成,而视图主要处理触摸事件和坐标系转换。
我不会在这里详细介绍Core Animation的工作原理,您可能需要阅读Core Animation Programming Guide了解更多信息。基本上,它是一种在图层树中动画更改的系统,而无需明确计算每个关键帧(从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
,因此如果不做任何其他操作,将不会进行任何动画。
现在,让我们在
UIView
子类中覆盖
setCenter:
:
@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
在这里,我们使用核心动画设置一个显式动画,该动画可以通过动画底层的
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
动画系统的确切工作方式,只是为了展示原理上它是如何运作的。