如何使用块的UIView动画在幕后工作

时间:2013-03-02 14:47:34

标签: ios objective-c uiview

我是Objective-C / iOS编程的新手,我正在努力了解UIView动画是如何在幕后工作的。

说我有这样的代码:

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

作为animations参数传递的东西是可以执行的Objective-C块(类似于其他语言中的lambdas / anonymous函数),然后它会更改alpha的{​​{1}}属性{1}}从当前值到1.0。

但是,该块不接受动画进度参数(例如从0.0到1.0或从0到1000)。我的问题是动画框架如何使用这个块来了解中间帧,因为块只指定最终状态。

编辑: 我的问题是label方法的引擎操作,而不是使用它的方式

我对animateWithDuration代码如何工作的假设如下:

  1. animateWithDuration为所有视图对象激活某种特殊状态,其中实际上没有执行更改但仅进行了注册。
  2. 执行该块并注册更改。
  3. 它查询视图对象的更改状态并获取初始值和目标值,从而知道要更改的属性以及执行更改的范围。
  4. 根据持续时间和初始/目标值计算中间帧,并触发动画。
  5. 有人能否说明animateWithDuration是否真的以这种方式运作?

3 个答案:

答案 0 :(得分:19)

当然我不知道幕后究竟发生了什么,因为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。从本质上讲,它是一个动画层树动画的系统,无需显式计算每个关键帧(实际上很难从Core Animation中获取中间值,通常只需指定值和持续时间等,然后让系统使用照顾细节。)

由于UIView基于CALayer,因此其许多属性实际上是在底层中实现的。例如,当您设置或获取view.center时,这与view.layer.location相同,更改其中任何一项也会更改另一项。

使用CAAnimation(这是一个具有许多具体实现的抽象类,如CABasicAnimation的简单事物和CAKeyframeAnimation,可以显式动画 对于更复杂的东西)。

那么UIView属性设置器可以在动画块中实现“神奇地”动画更改吗?让我们看看我们是否可以重新实现其中一个,为了简单起见,让我们使用setCenter:

首先,这是上面使用全局my_animateWithDuration:animations:的{​​{1}}方法的修改版本,以便我们可以在CATransaction方法中找出动画应该有多长时间取:

setCenter:

请注意,我们不再使用- (void)my_animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations { [CATransaction begin]; [CATransaction setAnimationDuration:duration]; animations(); [CATransaction commit]; } beginAnimations:...,因此不执行任何操作,不会动画。

现在,让我们覆盖commitAnimations子类中的setCenter:

UIView

在这里,我们使用Core Animation设置显式动画,动画制作底层的@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)); }]; 动画系统的工作方式,而是为了说明原则上的工作方式。

答案 1 :(得分:1)

未指定中间帧的值;在先前设置的值和动画块内设置的目标值之间自动生成值的动画(在这种情况下为alpha,但也包括颜色,位置等)。您可以使用animateWithDuration:delay:options:animations:completion:指定选项来影响曲线(默认值为UIViewAnimationOptionCurveEaseInOut,即值更改的速度将加速和减速)。

请注意,之前设置的任何值的动画更改都将首先完成,即每个动画块指定从前一个结束值到新结束值的新动画。您可以指定UIViewAnimationOptionBeginFromCurrentState以从已经进行中的动画状态开始新动画。

答案 2 :(得分:0)

这会将块内指定的属性从当前值更改为您提供的任何值,并在整个持续时间内线性地执行。

因此,如果原始alpha值为0,则会在2秒内淡化标签。如果原始值已经是1.0,则根本不会看到任何效果。

在幕后,UIView负责确定需要进行更改的动画帧数。

您还可以通过将缓动曲线指定为UIViewAnimationOption来更改更改的速率。同样,UIView会为您处理补间。