2

一开始我想知道为什么如果我们子类化UIView,并且我们实现了一个drawRect要调用的方法,但同时,如果我们在这个视图中添加按钮或标签,并调整它们的位置坐标,它们也会被重新绘制...... ..所以这个视图似乎有两种绘制机制,一种用于自身,另一种用于此视图中的对象。

但事实证明,似乎每 1/60 秒,整棵树就被遍历一遍。从顶视图对象开始,iOS 会访问所有的子对象,然后调用drawRectself子对象也以同样的方式被访问,孙子对象被访问,并且drawRect为每个第一级子对象调用,如下所示:

-(void) processViewObject:(UIView *) obj {
    // pseudo code:
    foreach children "c" already sorted by zOrder from most negative to -1
        processViewObject(c)    // recursion

    if ([self needsUpdateOrNot] == YES)
        [self drawRect]

    foreach children "c" already sorted by zOrder from 1 to the greatest number
        processViewObject(c)   // recursion
}

并且每 1/60 秒拨打一次电话,对于

processViewObject(topViewObject);    //  start from the topmost view object

这样任何视图都可以在drawRect和视图的子视图中绘制自己,如果它们的任何位置坐标被修改,或者如果它们的内容发生变化,那么脏位needsUpdateOrNot已经设置得更早,因此drawRect将调用它们来重新绘制自己。

或者实际上,如果设置了视图的脏位,那么可能该位将在递归中传递给子项,如下所示:

    foreach children "c" already sorted by zOrder from 1 to the greatest number
        processViewObject(c, [self needsUpdateOrNot])

这样如果设置了父级的脏位,那么子级也需要重新绘制,以便正确绘制该视图内的整个图片。

此外,每个漂亮的按钮或标签都只是一个UIView对象,drawRect已经实现了一个可以绘制漂亮图像的对象——按钮、复选框、标签或任何其他小部件。

这就是一切如何在屏幕上绘制的整体机制?我听说过“从不调用drawRect自己,而是让它被调用”,但从来没有完全理解为什么,但如果以上是整体机制,那么看起来就是这个原因。

我想知道这是否与Microsoft Windows或任何GUI操作系统上实际上几乎相同的机制相同,并且在某些游戏框架上,我认为无论是否设置了脏位,都会绘制所有内容,因为据说,事情在游戏中一直在移动,无论如何,框架只是每 1/60 秒绘制一次。

上面的zOrder处理顺序是在某个框架的代码中,所以先绘制所有负zOrder子项,然后self绘制,再绘制所有正zOrder子项。(这就是 zOrder 是如何以 Painter Model 的方式在屏幕上绘制事物的方式实现的——后者绘制的将掩盖之前绘制的。)如果相对于子项的 zOrder 也是如此——zOrder 的self数量如果它是相对于它自己的兄弟姐妹,而不是相对于它的孩子。0self

以上准确吗?如果给出答案,如果可能的话,是否可以引用部分源代码或参考,以便我们知道它是如何工作的标准或官方方式?

4

3 回答 3

6

我认为您将合成与绘图混淆了。这两个是不同的概念,它们在不同的点上发挥作用。

在 iOS 上,每个 UIView 都由一个 CALayer 支持,它实际上是一个围绕矩形 OpenGL ES 纹理的轻量级包装器。当一个 UIView 需要完全重绘时(第一次出现,或者在不同情况下强制重绘时),-drawRect:会触发并使用 Core Graphics 将矢量图形渲染为位图。然后该位图通过 CALayer 上传到 GPU 并存储在那里。

当需要移动视图或对其应用简单的变换(旋转、缩放等)时,不会重绘它,只需移动支持层并合成可见场景中的层。同样,每个视图和子视图都有自己的支持层,因此它们在视图(层)层次结构中按 Z 顺序组合。在 GPU 上进行合成是一项非常快的操作,比初始绘制视图要快得多。这就是在 iOS 中实现平滑滚动或动画的原因,因为这些底层矩形图像只是在 GPU 上移动并合成。这也是为什么在视图和图层中具有透明度会减慢滚动等操作的速度,因为合成非透明图层比不透明图层慢得多。

当您移动位于它们上方的子视图或其他视图时,不会重绘视图的重叠部分,因为合成会处理可见和不可见的内容。实际重绘发生的唯一时间-drawRect:是当您使用-setNeedsDisplay强制重新渲染或设置needsDisplayOnBoundsChangeCALayer 时,它会根据大小或形状的变化重新绘制自身。

我相信 jturton 链接到的部分文档在涉及 iOS 或 Mac 上的图层支持视图时是不正确的。它似乎是在 Mac 缺少 Core Animation 时编写的,并且是呈现非层支持的 NSView 的方式,因此它可能是从较旧的参考中复制和粘贴的。支持图层的视图不是这种情况,当部分被遮挡时,它们不会重绘自己。您可以通过-drawRect:在支持层的 UIView 中记录何时调用来自己测试这一点,并且您会发现当视图只是在彼此之上移动时不会触发它。

于 2012-05-18T17:44:57.797 回答
2

视图是从底部(z 方向)向上绘制的。不透明视图等有各种优化。所有标记为脏的视图都在运行循环结束时重新绘制 - 没有你怀疑的每 1/60 秒的机制。

参考:https ://developer.apple.com/library/ios/documentation/general/conceptual/Devpedia-CocoaApp/DrawingModel.html

于 2012-05-18T05:54:32.137 回答
1

来自jrturton 引用的文档

iOS 和 Mac OS X 中的绘图遵循两种技术路径之一,并基于视图按需更新其外观的模型

我认为这将解决定期更新观点的问题。视图只会按需更新,这是可以理解的,因为 iphone 仍然是一个嵌入式设备。处理器/内存越少越好。它也必须考虑电池寿命。

视图更新的一些情况(再次来自文档)

  • 用户移动或移除部分遮挡另一个视图的视图。
  • 用户将视图从包含的滚动视图中滚动出来,然后再将其滚动回来。
  • 代码通过将其 hidden 属性设置为 NO 使先前隐藏的视图再次可见。
  • 代码显式地使视图无效 要使视图无效并因此将其标记为重绘,您可以在视图上调用诸如 setNeedsDisplay 或
    setNeedsDisplayInRect: 之类的方法。

在主事件循环的一个周期结束时,窗口会沿着其视图层次结构向下移动,并通过调用它们的 drawRect: 方法请求这些视图自己绘制。

因此,在主事件循环中,窗口(顺便说一句,它是父级)遍历整个树层次结构,但只更新需要更新的子级。其他视图保持原样..

于 2012-05-18T06:19:07.413 回答