UIView中复杂的布局,可以为其内容添加动画效果

时间:2014-06-18 07:23:48

标签: ios iphone animation layout uiview

当我设计需要同时使用特殊布局和动画的复杂UIView时,我经常会遇到一个常见问题。

我目前正在构建的内容与邮件应用在iOS上用于在撰写电子邮件时显示收件人列表(蓝色徽章容器)的视图相同:

the view

所以我理解并知道如何构建这样的视图,但在面对这样的情况时我总是有这个问题:如何处理复杂的布局(布局所有蓝色圆角矩形)和它们的动画同时(添加或删除收件人时)?

1。通常我会重新实现:

- (void)layoutSubviews

根据我当前的界限反映布局时刻的视图状态(即并排布局每个蓝色圆形UIButton),并在有人向列表添加新人时添加动画UIButton。

但是如果布局传递已经在运行,动画会发生什么? (这可能是一个愚蠢的问题,因为一切都应该发生在主线程上,因此这里不涉及并发性,但我不确定如何处理这个问题)

我认为这两件事(布局和动画)不应该同时发生,因为主要的runloop"排队工作和队列工作"一个"块"一次(但也许一个动画只有几百个排队加入不同的东西超时,这可能很容易被一个布局块在其中妥协?)

2。此外,这个解决方案是否可以接受?

  • 重新实现layoutSubviews以完全相同的方式处理我的子视图的正确布局
  • 当有人添加或删除某个人时,只需将其调用以重新定位所有动画

    [UIView animateWithDuration:0.25f animations:^{
        [self setNeedsLayout];
        [self layoutIfNeeded];
    }];
    

正如你所看到的,这里有很多问题,我不知道如何妥善处理这个问题。

这两种解决方案都经过测试和批准,但问题是哪一种最好?

你有更好的解决方案吗?

感谢您的帮助!

1 个答案:

答案 0 :(得分:1)

据我了解,您的意思是应用于实施layoutSubviews方法的视图的动画,例如:其框架的动画变化。如果视图中的某些内容在动画正在进行时会导致视图重新布局,则会调用layoutSubviews方法。

基本上,这种动画只不过是:

  • 通过一些较小的值(例如,将原点的Y坐标增加1个像素)更改视图的帧(实际上是视图的帧的帧)
  • 如果需要,请致电layoutSubviews
  • 重绘视图(即其图层)
  • 通过一些小值更改视图的框架
  • 如果需要,请致电layoutSubviews
  • 重绘视图
  • ...
  • 重复上述步骤,直到视图的属性达到目标值

所有这些步骤都在主线程上执行,因此上述步骤是连续的,不是并发的。

一般来说,layoutSubviews方法应根据自己的边界定义子视图的坐标和大小。动画会更改视图的帧以及相应的边界,因此layoutSubviews方法的实现应该能够正确处理这些更改。

也就是说,您的问题的答案是正确实施layoutSubviews方法。

P.S。 This answer对动画的实施方式有很好的解释。


更新

以前我似乎错误地理解了这个问题。我的理解是:

  

如果为动画视图调用layoutSubviews方法,会发生什么?   也就是说,我认为有一些观点,例如通过动画更改其y坐标,在动画期间为此视图调用layoutSubviews方法,并且视图的某个子视图会更改其在layoutSubviews中的位置。

在@Nicolas澄清之后,我的理解如下:

  

让我们看一下带有子视图('Parent')的视图('Child');让我们为这个子视图设置动画('Child');在动画正在进行时,让我们调用layoutSubviews视图的'Parent'方法,并在此'Child'方法中更改layoutSubviews子视图的帧。会发生什么?

背景理论:

UIView本身不会呈现任何内容;它是由Core Animation层完成的。每个视图都有一个关联的CALayer,它包含视图的位图快照。实际上,UIView只是核心动画层之上的一个薄组件,它使用视图的位图快照执行实际绘制。这种机制优化了绘图:光栅化的位图可以通过图形硬件快速渲染;如果视图结构没有改变,那么位图快照就不会改变,也就是说它们被加入了#39;

核心动画图层次结构与UIView的层次结构相匹配;也就是说,如果某些UIView具有子视图,则与容器UIView对应的核心动画层具有与子视图对应的子图层。

嗯......实际上,每个UIView甚至超过1个对应的CALayerUIView层次结构生成3个匹配的Core Animation Layers树:

  • 图层树 - 这些是我们过去通过UIView' s layer属性使用的图层
  • Presentation Tree - 包含任何正在运行的动画的飞行中值的图层。尽管图层树对象包含动画的目标值,但表示树中的对象会反映屏幕上显示的当前值。您永远不应该修改此树中的对象。相反,您可以使用这些对象来读取当前动画值,也许是为了从这些值开始创建新动画。
  • 渲染树中的对象执行实际动画,并且是Core Animation的私有。

更改UIView的属性(例如框架)实际上是更改了CALayer的属性。也就是说,UIView的属性是相应CALayer属性的包装器。

UIView框架的动画实际上是CALayer框架的变化;层树的层的帧被立即设置为目标值,而层的帧值从表示树的变化被及时拉伸。以下电话:     [UIView animateWithDuration:5个动画:^ {             CGRect frame = self.label.frame;             frame.origin.y = 527;             self.label.frame = frame;         }]; 并不意味着self.label drawRect:方法将在接下来的5秒内被多次调用;这意味着y - 与CALayer对应的演示树self.label的坐标将在这5秒内从初始值逐渐变为目标值,self.label存储在此CALayer中的位图快照将根据其y坐标的更改重新绘制多次。

答案:

鉴于此背景,现在我们可以回答原始问题。

因此,我们为子视图提供动画,并为父视图调用layoutSubviews方法;在这种方法中,子视图的框架会发生变化。这意味着与子视图关联的图层框架将立即设置为新值。同时,来自表示树的图层具有一些中间值(根据正在进行的动画);设置新帧只会更改演示树图层的目标值,以便动画将继续到新目标。

也就是说,原始问题中描述的情况的结果是“跳跃”'动画。请参阅GitHub sample project中的演示。

此类复杂案例的解决方案

layoutSubviews方法中,如果动画正在进行,则需要使用飞行中的动画坐标。您可以使用与视图相关联的presentationLayer CALayer方法获取它们。也就是说,如果正在设置动画的视图aView,则可以使用[aView.layer presentationLayer]访问此视图的表示层。