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

MDJ*_*MDJ 16 objective-c uiview ios

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

说我有这样的代码:

[UIView animateWithDuration:2.0 animations:^{
    self.label.alpha = 1.0;
}];
Run Code Online (Sandbox Code Playgroud)

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

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

编辑:我的问题是有关,而引擎盖下的操作animateWithDuration方法,而不是使用它的方式.

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

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

有人能否说明animateWithDuration这种方式是否真的有效?

omz*_*omz 20

当然我不知道幕后究竟发生了什么,因为UIKit不是开源的,我不在Apple工作,但这里有一些想法:

在UIView引入基于块的动画方法之前,动画视图看起来像这样,并且这些方法实际上仍然可用:

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

知道这一点,我们可以实现我们自己的基于块的动画方法,如下所示:

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

...这与现有animateWithDuration:animations:方法完全相同.

从块中取出块,很明显必须有某种全局动画状态,UIView然后在动画块中完成动画时,可以使用这些动画状态对其(动画)属性进行动画处理.这必须是某种堆栈,因为您可以拥有嵌套的动画块.

实际动画由Core Animation执行,Core Animation在图层级别工作 - 每个动画UIView都有一个CALayer负责动画和合成的支持实例,而视图主要处理触摸事件和协调系统转换.

我不会在这里详细介绍Core Animation的工作原理,您可能需要阅读核心动画编程指南.从本质上讲,它是一个动画层树动画的系统,无需显式计算每个关键帧(实际上很难从Core Animation中获取中间值,通常只需指定值和持续时间等,然后让系统使用照顾细节).

因为它UIView是基于a的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];
}
Run Code Online (Sandbox Code Playgroud)

请注意,我们不再使用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
Run Code Online (Sandbox Code Playgroud)

在这里,我们使用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));
}];
Run Code Online (Sandbox Code Playgroud)

我并不是说这正是UIView动画系统的工作方式,而是说明它原则上是如何工作的.