带有块的 UIView 动画如何在幕后工作
Posted
技术标签:
【中文标题】带有块的 UIView 动画如何在幕后工作【英文标题】:How UIView animations with blocks work under the hood 【发布时间】:2013-03-02 14:47:34 【问题描述】:我是 Objective-C/ios 编程的新手,我正在尝试了解 UIView 动画的工作原理。
假设我有这样的代码:
[UIView animateWithDuration:2.0 animations:^
self.label.alpha = 1.0;
];
作为animations
参数传递的东西是一个可以执行的Objective-C 块(类似于其他语言中的lambdas/匿名函数),然后它将label
的alpha
属性从当前更改值为 1.0。
但是,该块不接受动画进度参数(例如从 0.0 到 1.0 或从 0 到 1000)。我的问题是动画框架如何使用这个块来了解中间帧,因为块只指定最终状态。
编辑:
我的问题是关于animateWithDuration
方法的幕后操作,而不是使用它的方法。
我对@987654326@ 代码如何工作的假设如下:
animateWithDuration
为所有视图对象激活某种特殊状态,其中并未实际执行更改而只是注册。
它执行块并注册更改。
它会查询视图对象的更改状态并取回初始值和目标值,从而知道要更改哪些属性以及在什么范围内执行更改。
它根据持续时间和初始/目标值计算中间帧,并触发动画。
有人能解释一下animateWithDuration
是否真的以这种方式工作吗?
【问题讨论】:
【参考方案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 Programming Guide 来了解它。本质上,它是一个在层树中动画变化的系统,无需显式计算每个关键帧(实际上从核心动画中获取中间值相当困难,您通常只需指定 from 和 to 值、持续时间等,然后让系统注意细节)。
因为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
在这里,我们使用 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】:没有指定中间帧的值;值的动画(在这种情况下为 alpha,还有颜色、位置等)在先前设置的值和动画块内设置的目标值之间自动生成。可以通过animateWithDuration:delay:options:animations:completion:
指定选项来影响曲线(默认为UIViewAnimationOptionCurveEaseInOut
,即数值变化的速度会加速和减速)。
请注意,任何先前设置的动画值更改都将首先完成,即每个动画块都指定一个从前一个结束值到新值的新动画。您可以指定UIViewAnimationOptionBeginFromCurrentState
以从已经进行中的动画状态开始新的动画。
【讨论】:
【参考方案3】:这会将块内指定的属性从当前值更改为您提供的任何值,并将在持续时间内线性执行。
因此,如果原始 alpha 值为 0,这将在 2 秒内淡入标签。如果原始值已经是 1.0,则根本看不到任何效果。
在后台,UIView
负责确定需要进行多少动画帧的更改。
您还可以通过将缓动曲线指定为UIViewAnimationOption
来更改更改发生的速率。同样,UIView
为您处理补间。
【讨论】:
以上是关于带有块的 UIView 动画如何在幕后工作的主要内容,如果未能解决你的问题,请参考以下文章