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

Pal*_*ndo 6 core-animation uiview objective-c-blocks

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

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

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

Dav*_*ist 16

它实际上是一个非常优雅的解决方案,它围绕视图是层委托这一事实而构建,并且独立层隐式地对属性更改进行动画处理.

恰好是几天前我在NSConference上给了BLITZ谈论这个问题,我在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];
Run Code Online (Sandbox Code Playgroud)

视图层协同作用非常优雅

特别是我想了解在那里使用Objective C的神秘特征来捕获动画块执行之前和之后的所有相关层状态变化.

实际上是另一种方式.视图构建在图层的顶部,它们可以非常紧密地协同工作.在视图上设置属性时,它会在图层上设置相应的属性.例如,您可以看到视图甚至没有框架,边界或位置的自己的变量.

观察黑盒实现,我认为它需要捕获动画块中修改的所有图层属性的前置状态,以创建所有相关的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;
}
Run Code Online (Sandbox Code Playgroud)

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

不(见上文)

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

是的,有点(见上文)

自己创建类似的API

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

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

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

  • 感谢您的全面解释! (2认同)

Dun*_*n C 6

大卫的回答很棒。你应该接受它作为最终答案。

我确实有一点贡献。我在我的一个名为“ Sleuthing UIView Animations ”的github 项目中创建了一个 Markdown 文件。(链接)它更详细地介绍了如何观看系统为响应 UIView 动画而创建的 CAAnimation 对象。该项目称为KeyframeViewAnimations。(关联)

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

而且,为了在应得的地方给予赞扬,大卫建议了我使用的技术。