在 Layer-Backed 视图上使用核心动画

Posted

技术标签:

【中文标题】在 Layer-Backed 视图上使用核心动画【英文标题】:Use core animation on an Layer-Backed view 【发布时间】:2014-01-20 05:11:34 【问题描述】:

在Core Animation Programming guide中,有一段关于How to Animate Layer-Backed Views,上面写着:

如果您想使用 Core Animation 类来启动动画,您必须从基于视图的动画块内发出所有 Core Animation 调用。 UIView 类默认禁用图层动画,但在动画块内重新启用它们。因此,您在动画块之外所做的任何更改都不是动画。

还有一个例子:

[UIView animateWithDuration:1.0 animations:^
   // Change the opacity implicitly.
   myView.layer.opacity = 0.0;

   // Change the position explicitly.
   CABasicAnimation* theAnim = [CABasicAnimation animationWithKeyPath:@"position"];
   theAnim.fromValue = [NSValue valueWithCGPoint:myView.layer.position];
   theAnim.toValue = [NSValue valueWithCGPoint:myNewPosition];
   theAnim.duration = 3.0;
   [myView.layer addAnimation:theAnim forKey:@"AnimateFrame"];
];

在我看来,如果我不从基于视图的动画块内发出 Core Animation 调用,就不会有动画。

但似乎如果我直接添加核心动画调用而不使用基于视图的动画块,它的工作原理是一样的。

我错过了什么吗?

【问题讨论】:

layer-backed view 和 layer-hosted view 之间的区别以及随之而来的所有怪癖仅适用于 OS X,并且与保持与 10.5 之前的 API 的兼容性有关。即使是 ios 的第一个版本(称为 iPhone OS(那个时代))也完全支持每个视图的核心动画层。 @DavidRönnqvist 这似乎不是问题的暗示。 (也许我可以说得更好)。文档中引用的简化版本是:“UIView 禁用了 implicit 动画,除了在动画块内。如果你想做 implicit layer 动画,你必须在动画块内做。 ”。 OP 在块内执行显式动画,这不是必需的。 ...也许我应该花一点时间回答这个问题,而不是发布模糊的 cmets ???? @DavidRönnqvist 结果显示它禁用了隐式动画,但在文档中没有提到它。 "UIView 类默认禁用图层动画,但在动画块内重新启用它们" 【参考方案1】:

tl;dr: 文档仅涉及 隐式 动画。显式动画在动画块之外也能正常工作。


我对文档的解释

文档中引用的简化版本类似于(我解释它):

UIView 已禁用 隐式 动画,但动画块内除外。如果你想制作隐式层动画,你必须在动画块内完成。

什么是隐式动画,它们是如何工作的?

隐式动画是指独立层的可动画属性发生变化时发生的情况。例如,如果您创建一个图层并更改它的位置,它将动画到新位置。默认情况下,许多图层属性都具有此行为。

它会发生这样的事情:

    系统启动了一个事务(我们没有做任何事情) 属性值已更改 图层查找该属性的操作 在某些时候事务被提交(我们没有做任何事情) 应用找到的操作

请注意,上面没有提到动画,而是“动作”一词。此上下文中的操作是指实现CAAction 协议的对象。它很可能是一些 CAAnimation 子类(如 CABasicAnimation、CAKeyframeAnimation 或 CATransition),但它是为与任何符合该协议的东西一起工作而构建的。

它怎么知道要采取什么“行动”?

通过在图层上调用actionForKey: 来查找该属性的操作。默认实现按以下顺序查找操作:

此搜索按此顺序进行(参考:actionForKey: documentation)

    如果层有一个委托并且该委托实现了访问层的过滤器方法,层调用该方法。代表必须执行以下操作之一: 返回给定键的操作对象。 如果它不处理该操作,则返回 nil。 如果NSNull 对象不处理该操作,则返回该对象并且应终止搜索。 图层在图层的actions 字典中查找。 该层在 style 字典中查找包含该键的操作字典。 层调用其defaultActionForKey: 方法来查找任何类定义的操作。 该层查找由 Core Animation 定义的任何隐式操作。

UIView 在做什么?

对于支持视图的层,视图可以启用或禁用操作by implementing the delegate method actionForLayer:forKey。对于正常情况(在动画块之外),视图通过返回 [NSNull null] 来禁用隐式动画,这意味着:

它不处理该操作,应终止搜索。

然而,在动画块中,视图返回一个真实的动作。这可以通过在动画块内部和外部手动调用actionForLayer:forKey: 来轻松验证。它也可能返回nil,这将导致层继续寻找动作,如果在此之前找不到任何东西,最终会以隐式动作(如果有的话)结束。

当找到一个动作并提交事务时,该动作将使用常规addAnimation:forKey: 机制添加到层。这可以通过创建自定义层子类并在-actionForKey:-addAnimation:forKey: 中登录,然后在自定义视图子类中覆盖+layerClass 并返回自定义层类来轻松验证。您将看到独立层实例记录了常规属性更改的两种方法,但支持层不添加动画,除非在动画块中。

为什么要对隐式动画进行这么长的解释?

现在,我为什么要对隐式动画的工作原理进行这么长的解释?好吧,这是为了表明它们使用的方法与您在显式动画中使用的方法相同。知道它们是如何工作的,我们就可以理解文档中所说的“UIView 类默认禁用图层动画但在动画块内重新启用它们”的含义。

UIView 没有禁用显式动画的原因是您自己完成了所有工作:更改属性值,然后调用addAnimation:forKey:

代码中的结果:

动画块外:

myView.backgroundColor       = [UIColor redColor];           // will not animate :(
myLayer.backGroundColor      = [[UIColor redColor] CGColor]; // animates :)

myView.layer.backGroundColor = [[UIColor redColor] CGColor]; // will not animate :(
[myView.layer addAnimation:myAnimation forKey:@"myKey"];     // animates :)

动画块内部:

myView.backgroundColor       = [UIColor redColor];           // animates :)
myLayer.backGroundColor      = [[UIColor redColor] CGColor]; // animates :)

myView.layer.backGroundColor = [[UIColor redColor] CGColor]; // animates :)
[myView.layer addAnimation:myAnimation forKey:@"myKey"];     // animates :)

您可以在上面看到,独立层上的显式动画和隐式动画在动画块的外部和内部都具有动画效果,但基于层的视图的隐式动画只会在动画块内部进行动画处理。

【讨论】:

【参考方案2】:

我将通过一个简单的演示(示例)来解释它。 在您的视图控制器中添加以下代码。(不要忘记导入 QuartzCore

@implementation ViewController
    UIView *view;
    CALayer *layer;


- (void)viewDidLoad

    [super viewDidLoad];
    view =[[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    [self.view addSubview:view];
    view.backgroundColor =[UIColor greenColor];
    layer = [CALayer layer];
    layer.frame =CGRectMake(0, 0, 50, 50);
    layer.backgroundColor =[UIColor redColor].CGColor;
    [view.layer addSublayer:layer];


-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    layer.frame =CGRectMake(70, 70, 50, 50);

看到在 toucesBegan 方法中没有 UIVIew 动画块。 当您运行应用程序并单击屏幕时,图层的不透明度会生成动画。这是因为默认情况下它们是可动画的。默认情况下,图层的所有属性都是可动画的。

现在考虑图层支持视图的情况。 把代码改成下面

- (void)viewDidLoad

    [super viewDidLoad];
    view =[[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    [self.view addSubview:view];
    view.backgroundColor =[UIColor greenColor];
//    layer = [CALayer layer];
//    layer.frame =CGRectMake(0, 0, 50, 50);
//    layer.backgroundColor =[UIColor redColor].CGColor;
//    [view.layer addSublayer:layer];

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    view.layer.opacity =0.0;

//    layer.frame =CGRectMake(70, 70, 50, 50);

您可能认为这也会为其视图设置动画。但它不会发生。因为默认情况下支持层的视图是不可动画的。 要使这些动画发生,您必须将代码显式嵌入到 UIView 动画块中。如下所示,

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    [UIView animateWithDuration:2.0 animations:^
        view.layer.opacity =0.0;
    ];

//    layer.frame =CGRectMake(70, 70, 50, 50);

这就解释了,

默认情况下,UIView 类(显然是由图层支持的)禁用图层动画,但在动画块内重新启用它们。因此,您在动画块之外所做的任何更改都不会被动画化。

【讨论】:

感谢您的回答!正如你所展示的那样工作。但是如果你使用CABasicAnimation而不是改变图层的动画属性,它会在没有[UIView animateWithDuration:2.0 animations:^ ];的情况下做动画。顺便说一句,并非 CALayer 的所有属性都是可动画的,例如 frame 是的,完全正确。CABasicAnimation 总是在有或没有 uiview 动画块的情况下工作。无论视图是图层支持还是它只是一个简单的 calayer 实例都没有关系。文档的意思是,只需设置一个属性,它是否会动画(核心动画)。 这似乎是事实。但是在文档中,并没有说明规则是针对隐式动画的。你能指出如何理解这一点吗? 在经历“如果你想使用核心动画类来启动动画,你必须发出所有核心动画调用”时,我也感受到了同样的术语问题。但是在最后一行“因此,您在动画块之外所做的任何更改都不是动画。”,它说更改。我认为这意味着属性。我对此不太确定。让我们等到有人对此说些什么。【参考方案3】:

好吧,我从未在视图动画块中使用显式核心动画。文档似乎根本不清楚。 大概意思是这样的,只是猜测:

如果您想为链接到 支持层,您应该将它们包装到视图动画块中。在里面 如果您尝试更改图层不透明度,则视图的图层不是 动画,但如果包裹到视图动画块中。

在您的 sn-p 中,您直接创建了一个基本动画,因此明确地创建了一个动画。可能文档只是想指出视图和图层之间的区别。在后者中,大多数属性的动画都是隐式的。 如果你写这样的东西,你可以看到区别:

[UIView animateWithDuration:1.0 animations:^
   // Change the opacity implicitly.
   myView.layer.opacity = 0.0;
];

这将是动画。

   myView.layer.opacity = 0.0;

这不会是动画。

// Change the position explicitly.
   CABasicAnimation* theAnim = [CABasicAnimation animationWithKeyPath:@"position"];
   theAnim.fromValue = [NSValue valueWithCGPoint:myView.layer.position];
   theAnim.toValue = [NSValue valueWithCGPoint:myNewPosition];
   theAnim.duration = 3.0;
   [myView.layer addAnimation:theAnim forKey:@"AnimateFrame"];

这将是动画。

【讨论】:

以上是关于在 Layer-Backed 视图上使用核心动画的主要内容,如果未能解决你的问题,请参考以下文章

在 ViewDidAppear 上更改核心动画持续时间似乎不起作用

使用核心动画层动画视图

如何使用核心动画创建导航控制器推送效果?

Layer-Backed NSControl 仍然调用 NSCell 绘图例程

转场/视图转换后 iOS 核心动画失败

在核心动画中,红框代替 UIImage 视图移动