UIView基于块的动画方法的内部实现

时间:2014-03-21 10:25:12

标签: uiview core-animation objective-c-blocks

自从他们在iOS 4中推出以来,我一直在想着UIView基于块的动画方法的内部实现。特别是我想了解在那里使用Objective C的神秘特征来捕获动画块执行前后的所有相关层状态变化。

观察黑盒实现,我认为它需要捕获动画块中修改的所有图层属性的前置状态,以创建所有相关的CAAnimations。我想它不会对整个视图层次结构进行快照,因为这样会非常低效。动画块在运行时是不透明的代码blob,所以我不认为它可以直接分析它。它是否用某种重新编码版本替换CALayer上的属性设置器的实现?或者是对此属性的支持是否更改了CALayers

内部深处的重新编码

为了概括一下这个问题,是否可以创建类似的基于块的API,用于使用某些Objective C黑魔法来记录状态变化,或者这是否依赖于知道并且可以访问被更改对象的内部在街区?

2 个答案:

答案 0 :(得分:16)

它实际上是一个非常优雅的解决方案,它围绕以下事实构建:视图是层委托,而且独立层隐式地执行动画更改属性。

恰好是几天前我在NSConference上给了BLITZ谈论这个问题,我发布了my slides on GitHub并尝试写下我在演示者笔记中说的或多或少的内容。

那说:这是一个非常有趣的问题,我不经常被问到。它可能有点宽泛,但我真的很喜欢好奇心


UIView动画在iOS 4之前存在

  

自从他们在iOS 4中推出以来,我一直在想UIView的基于块的动画方法的内部实现。

UIView动画在iOS 4之前存在,但风格不同(不再推荐使用,因为使用起来比较麻烦)。例如,可以像这样完成具有延迟的视图的动画位置和颜色。 免责声明:我没有运行此代码,因此可能包含错误。

// Setup
static void *myAnimationContext = &myAnimationContext;
[UIView beginAnimations:@"My Animation ID" context:myAnimationContext];
// Configure
[UIView setAnimationDuration:1.0];
[UIView setAnimationDelay:0.25];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];

// Make changes
myView.center = newCenter;
myView.backgroundColor = newColor;

// Commit
[UIView commitAnimations];

视图层协同作用非常优雅

  

特别是我想了解Objective C的神秘功能在那里用来捕获动画块执行前后的所有相关图层状态变化。

实际上是另一种方式。视图构建在图层的顶部,它们可以非常紧密地协同工作。在视图上设置属性时,它会在图层上设置相应的属性。例如,您可以看到the view doesn't even have it's own variable for the frame, bounds or position

  

观察黑盒实现,我认为它需要捕获动画块中修改的所有图层属性的前置状态,以创建所有相关的CAAnimations。

它不需要这样做,这个就是非常优雅的地方。每当图层属性发生更改时,图层都会查找要执行的操作(动画的更通用术语)。由于在视图上设置大多数属性实际上是在图层上设置属性,因此您隐式设置了一堆图层属性。

图层寻找动作的第一个地方是它询问图层委托(记录的行为是视图是图层委托)。这意味着当图层属性更改时,图层会要求视图为每个属性更改提供动画对象。因此视图不需要跟踪任何状态,因为图层具有状态,并且图层要求视图在属性更改时提供动画

实际上,这并非完全正确。视图需要跟踪某些状态,例如:如果你是否在块内,用于动画的持续时间等等。

您可以想象API看起来像这样。

注意:我知道实际的实现是什么,这显然 巨大的简化来证明一点

// static variables since this is a class method
static NSTimeInterval _durationToUseWhenAsked;
static BOOL _isInsideAnimationBlock;

// Oversimplified example implementation of how it _could_ be done
+ (void)animateWithDuration:(NSTimeInterval)duration
                 animations:(void (^)(void))animations
{
    _durationToUseWhenAsked = duration;
    _isInsideAnimationBlock = YES;
    animations();
    _isInsideAnimationBlock = NO;
}

// Running the animations block is going to change a bunch of properties
// which result in the delegate method being called for each property change
- (id<CAAction>)actionForLayer:(CALayer *)layer
                        forKey:(NSString *)event
{
    // Don't animate outside of an animation block
    if (!_isInsideAnimationBlock)
        return (id)[NSNull null]; // return NSNull to don't animate

    // Only animate certain properties
    if (![[[self class] arrayOfPropertiesThatSupportAnimations] containsObject:event])
        return (id)[NSNull null]; // return NSNull to don't animate

    CABasicAnimation *theAnimation = [CABasicAnimation animationWithKeyPath:event];
    theAnimation.duration = _durationToUseWhenAsked;

    // Get the value that is currently seen on screen
    id oldValue = [[layer presentationLayer] valueForKeyPath:event];
    theAnimation.fromValue = oldValue;
    // Only setting the from value means animating form that value to the model value

    return theAnimation;
}
  

它是否用某种重新编码版本替换了CALayer上的属性设置器的实现?

否(见上文)

  

或者对这个属性的支持是否会改变在CALayers内部某处进行重新编码?

是的,有点(见上文)

自己创建类似的API

  

为了概括一下这个问题,是否可以创建类似的基于块的API,用于使用某些Objective C黑魔法来记录状态变化,或者这是否依赖于知道并且可以访问被更改对象的内部在街区?

如果您想根据属性更改提供自己的动画,则可以创建类似的基于块的API。如果你看一下我在NSConference的演讲中展示的用于检查UIView动画的技术(直接询问图层的actionForLayer:forKey:并使用layerClass创建一个记录所有addAnimation:forKey:信息的图层)那么你应该能够充分了解视图如何使用图层来创建这种抽象。

我不确定录制状态的改变是否是你的最终目标。如果您只想做自己的动画API,那么您不应该这样做。如果你真的想这样做,你可能会这么做,但是你可以使用的动画基础设施(视图和图层之间的委托方法和回调)就像动画一样多。

答案 1 :(得分:3)

大卫的回答太棒了。你应该接受它作为明确的答案。

我确实有一个小小的贡献。我在我的一个名为&#34; Sleuthing UIView Animations的github项目中创建了一个markdown文件。&#34; (链接)它详细介绍了如何观看系统为响应UIView动画而创建的CAAnimation对象。该项目名为KeyframeViewAnimations。 (链接)

它还显示了记录提交UIView动画时创建的CAAnimations的工作代码。

并且,为了给予信用到期的信用,大卫提出了我使用的技术。