这实际上是一个非常优雅的解决方案,它建立在视图是层的委托并且独立图层会隐式地在属性更改时进行动画的事实基础之上。
恰好我几天前在 NSConference 上做了一场关于此的 BLITZ 演讲,并在 GitHub 上发布了我的幻灯片,并尝试将我在演示说明中说的写下来。
尽管如此:这是一个非常有趣的问题,我很少看到有人问到。它可能有些广泛,但我真的很喜欢好奇心。
iOS 4 之前已经存在 UIView 动画
自从它们在 iOS 4 中引入以来,我一直在想 UIView 基于 block 的动画方法的内部实现方式。
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使用了哪些神秘的功能来捕获动画块执行前后的所有相关层状态更改。
实际上是相反的。视图建立在层之上,它们密切合作。当您在视图上设置属性时,它会设置对应的层属性。例如,您可以看到视图甚至没有自己的frame、bounds或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上属性设置器的实现?
不是(见上文)
或者,这个属性更改的支持是否在CALayer的深处内置?
是的,有点像(见上文)
创建类似的API
为了将问题概括一下,使用某些Objective C黑魔法创建类似的基于块的API来记录状态更改是否可能,或者这是否依赖于知道并具有访问正在块中更改的对象的内部?
如果您想根据属性更改提供自己的动画,则绝对可以创建类似的基于块的API。如果您查看我在NSConference上展示的检查UIView动画的技术(直接询问图层的
actionForLayer:forKey:
和使用
layerClass
创建记录所有
addAnimation:forKey:
信息的图层),那么您应该能够学习足够多关于视图如何使用图层来创建这个抽象。
我不确定记录状态更改是否是您的最终目标。如果您只想执行自己的动画API,那么就不需要这样做。如果您真的想这样做,那么您可能可以,但是可用于动画的通信基础设施(视图和图层之间的委托方法和回调)将不如动画丰富。