Scroll Views 滑动原理http://objccn.io/issue

可能你很难相信和一个标准的UIView差异并不大,scroll view 确实会多出一些方法,但这些方法只是和 UIView 的属性很好的结合到一起了。因此,在要想弄懂 UIScrollView 是怎么工作之前,你需要先了解一下 UIView,特别是视图渲染的两步过程。

光栅化和组合

渲染过程的第一部分是众所周知的光栅化(rasterization),光栅化简单的说就是产生一组绘图指令并且生成一张图片。比如绘制一个圆角矩形、带图片、标题居中的 UIButtons。这些图片并没有被绘制到屏幕上去;取而代之的是,他们被自己的视图保持着留到下一个步骤使用。

一旦每个视图都产生了自己的光栅化图片,这些图片便被一个接一个的绘制,并产生一个屏幕大小的图片,这便是上文所说的组合。视图层级(view hierarchy)对于组合如何进行扮演了很重要的角色:一个视图的图片被组合在它父视图的图片上面。然后,组合好的图片被组合到父视图的父视图图片上面。视图层级最顶端是窗口(window),它组合好的图片便是我们看到的东西了。

概念上,依次在每个视图上放置独立分层的图片并最终产生一个图片,单调的图像更容易被理解,特别是如果你以前使用过像 Photoshop 这样的工具。我们还有另外一篇文章详细解释了像素是如何绘制到屏幕上去的。

现在,回想一下,每个视图都有一个和。当布局一个界面时,我们需要处理视图的 frame。这允许我们放置并设置视图的大小。视图的 frame 和 bounds 的大小总是一样的,但是他们的 origin 有可能不同。弄懂这两个工作原理是理解 UIScrollView 的关键。

在光栅化步骤中,视图并不关心即将发生的组合步骤。也就是说,它并不关心自己的 frame (这是用来放置视图的图像)或自己在视图层级中的位置(这是决定组合的顺序)。这时视图只关心一件事就是绘制它自己的 content。这个绘制发生在每个视图的方法中。

在drawRect:方法被调用前,会为视图创建一个空白的图片来绘制 content。这个图片的坐标系统是视图的 bounds。几乎每个视图 bounds 的 origin 都是 {0,0}。因此,当在光栅化图片左上角绘制一些东西的时候,你都会在 bounds 的 origin {x:0, y:0} 处绘制。在一个图片右下角的地方绘制东西的时候,你都会绘制在 {x:width, y:height} 处。如果你的绘制超出了视图的 bounds,那么超出的部分就不属于光栅化图片的部分了,并且会被丢弃。

在组合的步骤中,每个视图将自己光栅化图片组合到自己父视图的光栅化图片上面。视图的 frame 决定了自己在父视图中绘制的位置,frame 的 origin 表明了视图光栅化图片左上角相对父视图光栅化图片左上角的偏移量。所以,一个 origin 为 {x:20, y:15} 的 frame 所绘制的图片左边距其父视图 20 点,上边距父视图 15 点。因为视图的 frame 和 bounds 矩形的大小总是一样的,所以光栅化图片组合的时候是像素对齐的。这确保了光栅化图片不会被拉伸或缩小。

记住,我们才仅仅讨论了一个视图和它父视图之间的组合操作。一旦这两个视图被组合到一起,组合的结果图片将会和父视图的父视图进行组合,这是一个雪球效应。

考虑一下组合图片背后的公式。视图图片的左上角会根据它 frame 的 origin 进行偏移,并绘制到父视图的图片上:

CompositedPosition.x = View.frame.origin.x – Superview.bounds.origin.x;CompositedPosition.y = View.frame.origin.y – Superview.bounds.origin.y;

正如之前所说的,如果一个视图 bounds 的 origin 是 {0,0}。那么,我们得到这个公式:

CompositedPosition.x = View.frame.origin.x;CompositedPosition.y = View.frame.origin.y;

我们可以通过几个不同的 frames 看一下:

这样做是有道理的,我们改变 button 的frame.origin后,它会改变自己相对紫色父视图的位置。注意,如果我们移动 button 直到它的一部分已经在紫色父视图 bounds 的外面,当光栅化图片被截去时这部分也将会通过同样的绘制方式被截去。然而,技术上讲,因为 iOS 处理组合方法的原因,你可以将一个子视图渲染在其父视图的 bounds 之外,但是光栅化期间的绘制不可能超出一个视图的 bounds。

Scroll View的Content Offset

现在我们所讲的跟 UIScrollView 有什么关系呢?一切都和它有关!考虑一种我们可以实现的滚动:我们有一个拖动时 frame 不断改变的视图。这达到了相同的效果,对吗?如果我拖动我的手指到右边,那么拖动的同时我增大视图的origin.x,瞧,这货就是 scroll view。

当然,在 scroll view 中有很多具有代表性的视图。为了实现这个平移功能,当用户移动手指时,你需要时刻改变每个视图的 frames。当我们提到组合一个 view 的光栅化图片到它父视图什么地方时,记住这个公式:

CompositedPosition.x = View.frame.origin.x – Superview.bounds.origin.x;CompositedPosition.y = View.frame.origin.y – Superview.bounds.origin.y;

我们减少Superview.bounds.origin的值(因为他们总是0)。但是如果他们不为0呢?我们用和前一个图例相同的 frames,但是我们改变了紫色视图 bounds 的 origin 为 {-30, -30}。得到下图:

现在,巧妙的是通过改变这个紫色视图的 bounds,它每一个单独的子视图都被移动了。事实上,这正是 scroll view 工作的原理。当你设置它的属性时它改变scroll view.bounds的 origin。事实上,contentOffset 甚至不是实际存在的。代码看起来像这样:

– (void)setContentOffset:(CGPoint)offset{CGRect bounds = [self bounds];bounds.origin = offset;[self setBounds:bounds];}

注意前一个图例,只要足够的改变 bounds 的 origin,button 将会超出紫色视图和 button 组合成的图片的范围。这也是当你足够的移动 scroll view 时,一个视图会消失!

世界之窗:Content Size对困难的回答是胜利,对胜利的回答是谦逊。

Scroll Views 滑动原理http://objccn.io/issue

相关文章:

你感兴趣的文章:

标签云: